FEP-ae97: Client-side activity signing


This is a discussion thread for the proposed FEP-ae97: Client-side activity signing.
Please use this thread to discuss the proposed FEP and any potential problems
or improvements that can be addressed.


Existing Fediverse servers manage signing keys on behalf of their users. This proposal describes a new kind of ActivityPub client that lets users sign activities with their own keys, and a server that can distribute client-signed activities to other servers.


I’m not sure I understand the purpose of this. It seems like it just adds a DID as an authentication mechanism for AP clients. But it doesn’t seem to do anything with that identity. I would have expected it to serve as the base to make identity portable across AP servers, but it doesn’t as far as I can tell. So if it’s just an authentication mechanism, that doesn’t seem valuable enough to me to alter the core semantics of how the outbox works. I’m particularly concerned because the ID is a URI, and allowing the client to specify any arbitrary URI seems unwise.

1 Like

I don’t think that this a FEP that is ready for implementation yet, or ever will be. However, as I always say these are Fediverse Enhancement Proposals, so it is not necessary that something submitted as a FEP is ready for implementations.

Let’s move on to the topic of the FEP.

In my world, clients correspond to devices, i.e physical things owned by the end user. My understanding is that one wants to associate a different cryptographic identity to each of these devices [1]. So what I would like to see is something like an interface

  "registerIdentity": "https://server.example/register_identity",
  "verifyIdentity": "https://server.example/verify_identity",
  "addIdentity": "https://server.example/add_identity"

where addIdentity needs to have a workflow that ensures the different identities acknowledge each other. This then requires some analysis that attackers cannot add new identities.

[1]: One device breaks. You send it off to be repaired. You want to invalidate the cryptographic identity of the device before sending it off, and generate a new one for it afterwards.

1 Like

When client-side signing is used, recipients of activity can verify that it has not been changed by the server that delivered it. This might be useful if some sensitive data is being transferred. Recipients may also accept a signed activity if it was sent from a different server or directly from the client, though this is already possible with FEP-8b32 alone.

Yes, this FEP leads to portable identity, but this requires further research and I’m not sure if it is possible without breaking compatibility with existing fedi software. For example, one could use DIDs for actor IDs.

I agree, the server should not allow arbitrary IDs. Rather, IDs should match the URL template the server is using. I’ll update the proposal.

1 Like

I think one user account should correspond to one DID (DID = identity in the context of this FEP). In the simplest case the same signing key can be shared between all devices (this works even with did:key). If this is not acceptable, device keys should be added to DID document as additional verification methods, or some kind of capability based delegation might be used.

I’m not sure multi-device or multi-client complexity needs to be folded into the content signing/verification level-- that workflow for adding new devices with unique keys and rotating those keys doesn’t necessarily need to be foisted on all future consumers of the data and all other servers. The latter might be less willing to support multiple keys than something simpler, particularly if it breaks more of their status quo and creates more legacy problems for them. The least painful upgrade is something that allows non-upgrading custodial systems to peacefully coexist for a while with servers going live with client-side signing options.

Or, to put it another way, interop is better served by simpler DID Doc semantics, i.e. “this key is the current signing key, every time you fetch content from this user you might want to check back that it hasn’t been rotated by running the same query again, here’s the kind of signatures the current key produces”. A DID doc might include additional keys like the “recovery”/backup/rotation/update keys that many systems use, hashlinks or other verifiable links to previous versions of the doc, etc, but counterparties that don’t care and don’t use those functions should be able to ignore them in the confidence that it doesn’t affect their security. This is why a finite allowlist of DID methods (and thus a finite enumerated list of properties that can be safely ignored in them) may be preferable to some than “any DID” or “any URI” :smiley: This gets into a pretty dicey political game of line-drawing, tho. Perhaps the FEP doesn’t need to create a shortlist, but a separate informational/meta FEP can curate known examples and each implementation can choose how careful or closed-world to be.

I would mention that AFAIK, bluesky and nostr both have multiple keys that are relevant to other client<>server functions but only one that’s relevant for servers trying to verify content was actually produced clientside by that user before consuming or replicating it.


Nobody likes to use PGP due to being in this “simplest” case. It’s the simplest case to implement and the hardest case to use.

1 Like

It’s the simplest from the implementation perspective because nothing special needs to be done :slight_smile:
I agree this is not a good UX, and I hope we’ll find a better solution. Some form of delegation of authority from “master key” to device key or application key would be the best. In my opinion DIDs should represent personas, not devices.

This is the dominant pattern across the DID Methods I’ve worked on or audited (which is most of them). Enabling multidevice by delegating to (and revoking from) device-bound keys is the norm AFAICT-- see also Wallet Connect, WhatsApp, Discord, Telegram, iMessage, etc. Signal I believe goes the other way for various reasons, but it has its discontents and I suspect that MLS (aka “Signal v2”) might actually end up aligning more with the aforementioned, because they are going after smoother-UX use-cases and would thus need to support delegated/revocable multi-device as well. It’s also worth mentioning that WebAuthN/PassKey, which is coming down the pike fast, opens up similar UX even in browsers (delegating account privileges to unique browser-bound keys on each device for smoother UX), so I suspect this pattern to get even more dominant in coming years.

I would also add that some dayjob colleagues (including long-time fedi-nerd @ Bengo) have been working on a OAuth-like authZ system that’s less client/server and HTTP-bound called UCANs, if you’d like to avoid “rolling your own” multidevice delegation system and would like to use a common signing flow and receipt/logging structure for debugging and auditing. They even have a snazzy website outlining the pros and cons, what’s prod-worthy and what’s still moving, etc. There’s even some early work on using delegating UCANs (delegable capability objects) directly to device-bound WebAuthN device keys specific to a given service, if you want to get really cutting-edge!

1 Like

I would prefer ZCAPs or Verifiable Credentials because they use the same “integrity proof” building block we’re using in FEP-8b32 and FEP-c390. That makes implementer’s life a bit easier.

1 Like

Entirely fair for the “ActivityPub is a JSON-LD standard anyways” crowd! There is some amount of interop/translation possible between the two, but if you’re already doing Linked-Data style signing of content, it’s probably much easier to just reuse that tooling for the delegation stuff :smiley: . I think the main appeal of UCANs is interop with systems like WNFS that use them throughout, but that may be of interest to exactly zero of today’s AP implementations :sweat_smile:

@aumetra has been exploring the UCAN path:

It also means interop with Noosphere (uses UCAN), which is quite relevant to the fediverse as a ‘tool for thought’.

1 Like

Yep, I was exploring the direction of UCANs, mostly for their simple delegation properties.

Since you’re most likely gonna have a long-lived key representing your identity, you probably want to delegate as far away from that “root” secret as possible, to act as a preventative measure to somewhat counteract compromised identities


Is the following pattern of adding ?host=domain1,domain2 used in other applications (not necessarily Fediverse) already?


I haven’t seen this particular pattern anywhere. Magnet links is the closest thing I can think of, where query parameters are used to specify trackers and peers: bep_0009.rst_post


tr is a tracker url, if there is one. If there are multiple trackers, multiple tr entries may be included. The same applies for x.pe entries.

1 Like

Proposal has been updated: #166 - FEP-ae97: Update proposal - fep - Codeberg.org

I added some clarifications regarding object ID validation, added requirement to sign wrapped objects and an intentionally vague section on server-independent object IDs (this section may evolve into a separate FEP).

After rereading Decentralized Identifiers (DIDs) v1.0 I think the host fits quite well into the concept of did-parameters. One probably needs to work out a lot of details like ids that agree except the hosts parameters are the same, e.g. for updates.

However, I think it should be pretty clear how to build an application associated with a did, that publishes under that did, where signatures ensure validity of the documents.

1 Like

I’m thinking about connecting actors identified by DIDs with the rest of the network. This can be done by an activity translation service, but the challenge is in generating webfinger addresses for such actors.

One possible solution is percent encoding, but Mastodon currently doesn’t allow % in usernames (see this issue Percent encoding and `~` are not handled for accounts · Issue #17222 · mastodon/mastodon · GitHub).

To work around this, we can use some combination of allowed special symbols (.-_). Libervia performs translation of XMPP identifiers using this technique: https://repos.goffi.org/libervia-backend/file/970b6209526a/libervia/backend/plugins/plugin_comp_ap_gateway/__init__.py#l698

For example, this actor ID:


can be translated into this fediverse address:


(proxy.example is a domain where activity translation service is hosted)


This section has indeed evolved into a separate proposal: FEP-ef61: Portable Objects (discussion). I’m not removing this section yet, just adding a notice: #210 - FEP-ae97: Add deletion notice to "Server independent IDs" section - fep - Codeberg.org

1 Like