Reconciling different roots of identity

In the recent issue triage by @eprodrom today, we concluded with an issue item to explore the idea of writing a FEP formally describing Mastodon’s usage of the Move activity for follower migration, as a sort of “step 1” before standardizing any improvements in a “step 2”. However, there is a sort of “step 0” that I think should be addressed, and that is that the “root of identity” is not consistent or clear between different implementations. There are fundamentally 3 different roots:

  • The id, which is expected to be an HTTPS URI. Essentially, this option leans into the strengths and weaknesses of the WWW.
  • Some cryptographic keypair. There is the possibility of using did:key for this, and FEP-c390 tries to bridge this back to the HTTPS id via the same mechanism as profile fields (currently being formalized in FEP-fb2a): an attachment on the actor.
  • The username+domain pair. Mastodon uses this as canonical and authoritative identity throughout its codebase, and strictly expects that this pair will resolve via Webfinger acct: URIs.

The mismatch between different “roots of identity” leads to subtle weirdness when two systems of different roots interact with each other. For example:

  • In id-rooted and webfinger-rooted systems, you can perform something known as “blind key rotation”, in which the associated public key is swapped out silently in order to provide deniability for any past activities. This obviously breaks in a key-rooted system.
  • In an id-rooted system (where the id does not depend on username) or in a key-rooted system , the username can be changed at any time, which obviously breaks in a username-rooted system.
  • In a username-rooted or key-rooted system, the HTTPS URI can be changed at any time, which obviously breaks id-rooted systems that depend on HTTPS URIs.

In such breakages, the “same” actor is now considered a different actor in only some systems, but not in others. What should be done about this? This has knock-on effects for the concept of “migration”:

  • In an id-rooted system, the key or username can change without necessitating a migration. Migration is only necessary when the actor is “different”, i.e. has a different id. (This can be ameliorated by checking for and respecting HTTP redirects.)
  • In a key-rooted system, the id or username can change without necessitating a migration. Migration is only necessary when the actor is “different”, i.e. has a different key. (This can be sidestepped by maintaining a log of key rotations.)
  • In a username-rooted system, the id or key can change without necessitating a migration. Migration is only necessary when the actor is “different”, i.e. has a different username or domain. (Caveat: Mastodon is not fully rooted in usernames, and partially uses id in some places, particularly for objects and activities.)

It seems like the most “canonical” thing to do would be to take the id-rooted path, but support DIDs. In the DID spec, alsoKnownAs allows for bridging to other schemes or identifiers. You could have an id of the form did:key and an alsoKnownAs containing its current HTTPS URI(s), but any move away from HTTPS would need to extend ActivityPub such that the HTTP GET/POST behavior has an alternative that works and can be relied upon consistently. You would basically need a DID method that can be transformed into an HTTPS endpoint. Also you would need to decide which DID methods are acceptable and should be supported.


I don’t understand what this has to do with the Move specification. Can you elaborate a little bit on how this might impact implementers? If actors want to send a Move they can and if they don’t they won’t. I don’t see how this discussion would impact what actors need to do when receiving a Move activity

In practice we already see discrepancy between Move activities in, say, Mastodon vs. Mitra. Mastodon has the old account send the Move, verified by implicit authority and alsoKnownAs on the new account pointing back to the old account. Mitra wants the new account to send the Move, verified by a signed statement from the same key as the old account.

Yes, that’s what I’m thinking too. It is possible to design a protocol similar to ActivityPub with DIDs as IDs, chatternet attempted to do so. But it won’t be compatible with existing Fediverse.

A useful way to think about the “mixed” approach to identity is to view DIDs as representations of external authority. In both id and username-rooted systems, the domain owner is the authority. But with DIDs, the primary authority is elsewhere, and control is delegated to domain owner. There is a one-to-many relationship between external identifier and domain-bound Fediverse accounts. The way external identity is linked to accounts is not necessarily a digital signature, it could be some other mechanism like rel=me.

In the latter case, when the old account receives Move activity, it can re-broadcast it, making two migration strategies complementary.

1 Like

Assuming that DIDs were promoted to the canonical ID schema of choice for the protocol, where do we envision those DIDs being registered?

Also: I want to emphasize the importance of this warning from the spec:

If a DID method specification is written for a public-facing verifiable data registrywhere corresponding DIDs and DID documents might be made publicly available, it is critical that those DID documents contain no personal data.

Particularly given that we’re discussing this in the context of Threada potentially federating via AP, it’s important that that we aim for maximal privacy in implementing DIDs—or any form of canonical identity, really—so as to avoid increasing AP users’ vulnerability to Meta’s data collection practices.

1 Like

This assumes the original server is alive and can handle that. If it goes down, only the second strategy can work. But the second strategy assumes some external root of identity that the new account can claim is the same as the first account. Hence, this thread exploring identity and how to establish it. It doesn’t really help to have migrations that fail for entire classes of implementations. Some alignment, if possible, would help reduce the problem.

There’s a DID method registry, but you’re not obligated to support all methods, I don’t think.

Whichever server is presenting the AS2 document doesn’t have to present it in its entirety to everyone. You can already discriminate based on access control or authorization levels.

Correct. Nor is “choosing a DID method” required. DID methods are just URI schemes used by key-document systems, explaining how to generate and parse those key documents; only some of those systems publish DIDs in any queryable data store. I would also note that there is no way to add “alsoKnownAs” to a did:key or, for that matter, to many kinds of DIDs-- each DID method has different syntax and constraints, and the alsoKnownAs property is an optional property only implemented in some of them.

I have a strong preference for id HTTPS URIs to remain the canonical root of identity in the Fediverse, even though I am a huge fan of keypair-based identity systems, because, because this approach requires the least total development for all major implementations to align.

I would support a more tightly-aligned and opinionated identity model (ideally with one or more per-account keypairs as a first-class capability of every actor) in some future major version of AP, support for non-HTTPS URIs (ipfs:// for example), etc., but that’s a huge conversation. And even if it weren’t, it’s also a huge refactor for most implementations, and I would rather not make such drastic shifts too quickly. I assume no one is proposing such drastic changes be made prerequisite to aligning on migration mechanics, but I would advise against it if they did! We need migration mechanic alignment far sooner than we need fully-featured DID integrations.

I would prefer to think of a keypair (whether expressed as a did:key, a multikey, a did:pkh, and/or as any other form of did) as a property of the account that can be updated over time-- I don’t believe this is explicit enough in ActivityPub, but it seems implied, if nothing else, by the nature of ids in Linked Data generally as roots of identity. Keypairs being secondary properties that change over time is, I would argue, the dominant model in most software day; making them primary would require drastic changes to most Fediverse implementations, AND it would make necessary aligning on rotation mechanics, etc etc. Assuming an account can update keys periodically and that today’s keys can only be gotten by querying the server in its id feels a lot simpler than any alternative, particularly given status quo implementation-wise.
Thinking of the keypair as a property of the account also opens up “byo authenticator” patterns, i.e. replacing the public key in the profile without generating a new private key or importing an external private key, by proving control of it (e.g. signing an arbitrary confirmation-message payload and producing a verifiable receipt at time of import/association). (For a fun example of “byo authenticator” see this project that uses an Ethereum wallet as an authenticator for a Nostr account!)

This delegation to an external authenticator (such as Authy or Microsoft Authenticator, or other cryptographic wallets that can sign arbitrary messages) could align quite well with the approach in 521a and c390.

1 Like

I would agree-- a AP HTTPS URI doesn’t have any PII in it, if instance admins disallow and moderate against usernames containing government-names (whatever you do don’t look at my Fediverse handle, lol).

Any time you connect two long-lived identifiers, you get the worst of both privacy wise, or to put it another way, your privacy liabilities exponentially compound one another. I think the “BYO” approach is no exception, and when the authenticator you bring is dragging behind it financial records on public ledgers, long-lived identifiers, other accounts on public systems, etc, it’s sub-optimal from a privacy point-of-view. But giving non-programmers private keys to manage is always kind of a “with a great power comes great responsibility” situation, right?

If we specify HTTPS URI as canonical, then this is going to forever bind us to the shortcomings of the HTTPS protocol, particularly in how it is bound to an “authority component” stemming from DNS domain names, and a “path component” that also cannot change. This is perhaps not insurmountable, but it is a centralizing force in the long-run because the most feasible way to surmount it is to have a single authority stemming from a single domain name. Even in the DID space, Bluesky’s ATProto punted on this by setting up the Placeholder DID Method, did:plc:, which mints and resolves identifiers based on a single centralized “placeholder server”. ATProto also supports did:web, but notably does not allow for migration or data recovery… just like the https: World Wide Web itself. Some kind of canonical identity is needed that is external to the domain on which your server currently runs, if you want any sort of resilience to the original server or domain going down or becoming unavailable or not cooperating.

I’m not proposing fully-featured DID integrations, but I am proposing a general alignment on whether, when, and how to consider the id as non-authoritative, and what to consider authoritative in its place. At least something to consistently refer to and identify a resource as being “the same as” another resource. This would also allow for obtaining the resource from more than one server, and considering multiple servers as “authoritative” given that they are using the same source of truth. Think of how multiple different IPFS gateways can return the same file, but the HTTPS URI will depend on which gateway you used.

Basically, you could imagine something like a DHT which points to your “current location”, but this would require a network-wide agreement on using such infrastructure. You could also imagine something like a centralized nameserver which did nothing but mint and resolve identifiers via HTTP redirects, akin to a PURL service but with an API and support for ActivityPub content negotiation… but this again requires a network-wide agreement on using such infrastructure. Any solution you come up with would require the same. And ideally, in the same way that we have multiple DNS resolvers, we could really use something like multiple Webfinger resolvers, but crucially allowing for any resource and not just acct: resources. There’s a lot to learn from the way that ATProto handles the concept of a “personal data server” or PDS.

You could do an escrow model, but yeah, this still requires agreeing beforehand that the cryptokey scheme and infra should be used as more authoritative than the HTTPS id. Bringing it back to how account “migration” is done, via the Move activity, you basically need some acceptable verifiable proof that the new account has the same controller as the old account. This can be done via bidirectional links, cryptographic proofs, etc. But all these schemes need to be “blessed” somehow by various fediverse implementations, or else they won’t work in a useful manner. For example, Streams implements nomadic identity based on the cryptographic signature of the ids. You can export and import or otherwise copy your keys to another server, and both servers are considered authoritative, because both servers have your private key and can sign messages on your behalf. If Mastodon encountered both of these actors, it would consider them different actors, despite them being the same on the Zot/Nomad networks. This is signalled via alsoKnownAs from the DID spec (and ActivityStreams namespace), which signals that the given identifiers are used for the same resource. However, this runs directly contrary to how Mastodon is currently using alsoKnownAs for its “account aliases” feature which is used as a prerequisite for verifying a Move activity is valid. More here: Defining alsoKnownAs - #30 by trwnh


OH thank you so much I was missing the point of a lot of these threads, thanks for patiently pointing it out. I agree with

And I agree with you in that same thread that an instance operates as an authority, not as a controller, and that implicit or underspecified in current practice is an assumption of a client/server relationship authorizing that authority. This is convincing me to adopt your suspicion of HTTPS URI being enshrined forever! This could cause real problems for interop with P2P architectures beyond HTTP.

IPFS URIs (or the IPNS URI subset of that URI namespace, or DID URIs) are definitely the upgrade path I was looking to long-term, but I agree that the short-term path requires alignment on relational semantics and buy-in from major implementations, 1000%. So maybe more on that later, since short-term alignment is way more urgent.

As for the alsoKnownAs chaos, I am convinced that the rel=me system makes the most sense for one logical human maintaining multiple accounts over time, which is of course a major usability feature of modern social media anyways independent of migration and tombstoning.

What I would propose for the interrelated problems of migration and tombstoning is something very different and more hierarchical, which is closer to the DID semantic controller, i.e., that identifier HAS AUTHORITY OVER this account/identifier. For example, it could mean:

  • this authenticator/signer/private key has been authorized to sign for the subject anywhere its signatures are accepted,


  • this external identifier/authN mechanism is authorized to authenticate migration requests,


  • this external AuthN is authorized to tombstone this account, after migration, or keep it as a rel=me,

AND (perhaps riskiest for buy-in from today’s implementations)

  • :hot_pepper: if this server bans the user and freezes/closes their account, this authority has already been externalized and migration can still happen if records of the authorization can still be verified.:hot_pepper:

If instances can get OK with that pre-authorization/authority-export which overrides, say, a ban or a boot then maybe AP isn’t locked into the authority of the webserver forever?

Have I understood the most relevant open questions or am I still missing some?


yes. external with respect to the domain, which so far was the sovereign boundary for the fediverse.

Within a domain, the *id controller" and “id subject” (borrowing DID terminology) are different. One is the owner of the domain/instance (e.g. vs and the other is the person owning the account which is registered on the instance/domain.

DID Controller and DID Subject in our scenarios are likely the same (I’d must they are the same, actually). The controller is the person which owns the account/id on an instance (which here I consider the same as domain, correct?) that can act against the various did operations. The subject normally is either the person themselves, or a bot the person controls.
In a wider case, the controller is a group of people, and the subject is the group itself.

I say that because in this case - I think - the external controller/authority is the user/account owner themselves (self-sovereign identity).

And I think this is an important concept for the fediverse. the external controller, above the domain controller, is the user themselves. Especially if fediverse adopt DID, I think would be a mistake drifting away from this concept.

We are talking about an authority across the fediverse (or multiple fediverses), above each domai/instance authority. (hence calling external authority feels reductive and maybe incorrect).

I see that here and elsewhere there are references to did:key as DID method of choice.

I wonder if, given that we are introducing a new level of authority within the fediverse, and this should not be underestimated, we might want to consider a new DID method for the fediverse.

After all as @by_caballero (I think) says

DID methods are there to manage the identifier operations: create, update, reject documents, that are the way to make the claims required.

So a did:fediverse would specify the steps to perform those operations within a fediversion of DID.
I am not particularely keen to add a method, but my (newbie) intuition tells me there is space for this to be needed/preferred to bare did:key.

and likely it might be an inheritance/extension of did:key but with more to it.

But to understand if we really need it or it’s just over-enginnering something we should have simpler, we need to have clear the use-cases and objectives for which we are doing this

This thread underlines one use case, which if I understand correctly, in order to initiate a Move request from the new account, we need to have this form of authoritative “both accounts are about the same subject”.

I am in the process of writing down those use cases, I am sure that more can be added and some will be in conflict with each other or too difficult/perspective. Maybe most of those have been discussed and discarded already, but I couldn’t find a single place for it. so at least worth summarising ideas in one thread.

nomadic identify


Totally agreed! I think I’ve done enough “thinking in public” to stop wasting the lists’ time and contribute to FEPs and use-case documents now. Happy to look at a working draft of those use-cases before you post them-- I think in another thread I volunteered to try documenting use-cases as well, so it would probably behoove me to wait until you’ve shared a draft privately or publicly to avoid work that’s redundant to or distracts from yours!

yeah, this is it. “same subject” or “canonical subject” can be established like so, currently:

  • id is an exact match (URI equivalence)
  • id is present in alsoKnownAs (according to the DID definition of it, not Mastodon’s usage of it)
  • username + domain is a case-insensitive match (largely a Mastodon quirk; imo, should not take precedence over id)

this allows us to dedupe resources, say, we know this object/post/actor/etc is the same, we’ve uniquely identified it. XRD/JRD subject vs aliases is the same concept.

however, we also need to establish “same controller”, “external owner”, “same logical person” or at the very least “is permitted to send activities partially or fully on my behalf, particularly the Move activity”. some proposals:

  • check for alsoKnownAs (Mastodon usage, not DID definition)
  • check for rel-me or some other property making the same claim (unambiguous, but not currently supported by anyone since alsoKnownAs is historically used in a similar/same way)
  • check for a cryptographic signature that can be verified with the same public key as was last known (requires careful handling for edge cases, plus agreement that keys are meaningful sources of identity)

note that in the immediate above statement, we must differentiate between “same controller” and “permitted to send activities on my behalf”; the former might imply the latter, but the latter does not necessarily imply the former. we might grant or delegate or give escrow to some other controller who can act “on behalf of” us. for example, an “instance actor” representing a server or domain, which we are currently using. with such a scheme, it becomes possible to eliminate the shell game taking place within current http/ld signatures – rather than the server generating keypairs for every actor and then puppeting those actors, the server could send messages “on behalf of” the actors it “owns” or “is responsible for”. the exact nature of the relationship between any actor and their server (or any other actor) should be possible to clarify along these lines, and to place limits on what this relationship does or does not allow them to do.

1 Like

Yes, I completely agree that modeling the individual<>instance authority negotiation is the :hot_pepper: spicy bit :hot_pepper: ; it needs to be crystal clear for migration from a dead server to work (a use-case near to my heart).

I would just like to emphasis what I meant by “heirarchical” (controller) as opposed to “horizontal” (rel=me or alsoKnownAs) in case that didn’t make sense or answer the question as much as I thought it did. Those diagrams I linked to above of a Nostr client based on Sign In With Ethereum is a good example of a delegation pattern that could be relevant here: the client basically generates a deterministic Nostr private key to use on behalf of the user after receiving a delegation message signed by an EVM/secp key that the user controls elsewhere. This means the nostr key is downstream of that key “external” to it, which is its controller in DID terms. Thus, a “custodial” (well, technically deterministic/symmetrical, but at the very least server-side-generated) Nostr private key is signing things on behalf of a Nostr “actor” that has an external controller which outranks it (the ethereum private key it doesn’t own). This is not an alsoKnownAs or rel=me peer, but a greater authority.

Note: this arrangement today can be called “polite software” because you have to trust the Dostr implementation to enact its own revocation, which is a little wierd as a trust model. In theory, that delegation from controller-key to server-controlled key is revocable. To make that revocability more auditable in the code, it could just require a fresh signature from the controlling wallet at each session instead of persisting the delegation and requiring a second signature to revoke it (that would at least make the “politeness” a bit easier to trust).

I think that maps 100% to your “permitted to send activities on my behalf”? The tricky part is that Server A (being migrated away from) needs to somehow notarize a key it doesn’t controller as the ultimate controller of one of its accounts and cede that authority before going dark, AND that notarization needs to be persisted outside the server (on a neutral storage substrate like IPFS, or in the authenticator but verifiable because signed by the server, etc.) This might be the hardest pill for some implementations to swallow!