FEP-ef61: Portable Objects


This is a discussion thread for the proposed FEP-ef61: Portable Objects.
Please use this thread to discuss the proposed FEP and any potential problems
or improvements that can be addressed.


Portable ActivityPub objects with server-independent IDs.


I’m updating the proposal: #224 - FEP-ef61: Update proposal - fediverse/fep - Codeberg.org

The /.well-known/apkey endpoint works similarly to a DID resolver, so I think it makes sense to align its specification with Decentralized Identifier Resolution (DID Resolution) document.

I’m also wondering if did:apkey should be changed to did:ap:key (providing a common base for did:ap:web and others).

A quick nitpick: both the examples in this FEP have

"@context": "https://www.w3.org/ns/activitystreams"

They should include "https://w3id.org/security/data-integrity/v1" as well.


Out of curiosity, is there a specific design reason why there’s use of aliases instead of alsoKnownAs?

That would conflict with other uses of alsoKnownAs. I talked about this recently in adjacent topic:

The name of this property can be changed (to sameAs?), but it should be distinct from alsoKnownAs.

1 Like

Just as a mentionable find: apparently alsoKnownAs is in the DID core spec as an optional way to indicate other identifiers for a DID subject (actor?): Decentralized Identifiers (DIDs) v1.0

Interestingly in the JSON-LD context for DIDs (https://www.w3.org/ns/did/v1) it actually aliases to the ActivityStreams ID for alsoKnownAs right now:

  "@context": {
    "@protected": true,
    "id": "@id",
    "type": "@type",
    "alsoKnownAs": {
      "@id": "https://www.w3.org/ns/activitystreams#alsoKnownAs",
      "@type": "@id"

yes, this is the source of the conflict described in Defining alsoKnownAs - #30 by trwnh

essentially, DIDWG and SWICG had a joint meeting in 2021 to formalize that DID would use as:alsoKnownAs (and that the official activitystreams context would adopt as:alsoKnownAs). this led to the situation we currently have, where the property is in the activitystreams namespace but is actually defined in the DID spec. and of course, to further complicate things, mastodon had already been using the as:alsoKnownAs property unofficially for some years prior… with a slightly different semantic usage. where the DID spec defines it as “different identifiers for the same subject”, mastodon’s usage is more like “different subjects with the same controller”. i really wish that mastodon had gone with some other method of establishing requirements for a Move activity, such as rel-me links in the actor’s attachment fields.

a DID subject doesn’t have to be an actor, btw, it’s just the thing that a URI would be identifying, and the corresponding resource would be describing.

1 Like

Update: #240 - FEP-ef61: Update proposal - fediverse/fep - Codeberg.org

Switching to did:ap:key:

did:ap is a DID method that can be used to add DID URL functionality to other types of DIDs.

This change creates a boundary between identities (represented by ordinary DIDs) and data (did:ap and DID URLs). One can imagine similar constructions such as did:ap:web, did:ap:pkh and others.

Update: #275 - FEP-ef61: Update proposal - fediverse/fep - Codeberg.org

I added some notes on backward compatibility and property names.

i’m still a little confused by this, because it just doesn’t sit well with my mental model of DID syntax. DID methods are not composable or layerable-- each DID method turns DIDs into DID Documents, and adding functionality to a pre-existing DID method is not something the DID spec defines a way to do; conversely, DID URLs and implementations that dereference them ARE already specified in the DID spec here in a way that applies across all methods. If I understand it correctly, a DID URL ending in /actor/inbox could perfectly well dereference to a contentStream that is just the Actor’s inbox object (and appropriate mediatype to honor AP content negotiation!), regardless of whether the DID method and document provide a route to that contentStream or not (i.e., in cases like did:key where no service endpoints or non-key-material data can be gotten from the DID doc itself, a resolver could take URLs with paths and/or query parameters and still dereference them to the actor/inbox/content/etc, without breaking anything about the DID spec or DID resolution [draft] spec).

If you’re defining a way of handling URLs that overrides what pathing capabilities a given DID method does or doesn’t haven’t, wouldn’t that be a protocol handler, rather than an over-riding meta-DID-method? i.e. wouldn’t the syntax be
, since did:key:z6... still dereferences to the same DID object, and adding /path/to/object to it is still a valid DID URL but confuses and confounds a normal did:key resolver which doesn’t accept paths? Calling that did:ap:key: implies there is a did:ap method and resolver type, but it feels more like a protocol for handling DID URLs that match a certain pattern regardless of the DID method used.

Very few DID methods use paths at all (did:cheqd and did:sov are the only two that come to mind?), but if we switch from defining did:ap to defining ap://did:example, we might want to define how the latter parses DID URLs where example might have a valid path, since those are kind of being overridden.

i would also add that there are few DID query parameters defined across all DID methods by the DID core spec itself. These are “fair game” to use with any DID methods, including DID methods like did:key with deterministic resolution which could not, by themselves, do anything with those parameters. Some DID methods have special semantics and affordances for them (did:3 and did:sov, for example, offer certain guarantees for versionTime and versionId, and their official VDR-connected resolvers will resolve a DID differently if comes decorated with those query params!), but query parameters sit kind of “on top” of the DID method in all other cases where the DID method/resolver makes no special affordance for them.

did:ap is a regular DID method, with a resolution algorithm that says “remove the :ap segment and resolve the remaining DID”. As far as I can tell, the spec doesn’t forbid that.

Yes, a new URI scheme is another option, but why can’t did:ap method support paths? Technically it’s not an override, but a completely different DID method.

I’ve re-read the “DID URL Dereferencing” section you mentioned, but I still don’t understand how exactly the mechanism proposed in FEP-ef61 is against the DID spec.

DID URLs are shorter than ap:// URLs, and can be appended to HTTP URLs without escaping, so I would prefer to keep using them.

Well I suppose it comes down to how you interpret this sentence:

  1. A DID method specification MUST define exactly one method-specific DID scheme that is identified by exactly one method name as specified by the method-name rule in 3.1 DID Syntax.


did:ap:key:z6... contains TWO method-name segments, and each did:ap is literally a DID within a DID. Technically I suppose you’re defining the entire, valid-according-to-another-method DID as the method-specific-id segment and then adding a path parsed according to the “outer DID”, but recursing one DID into another DID feels like it breaks the spirit, if not the letter, of the spec, and in the case of DID methods that DO allow/afford/define pathing, is literally overloading that path in ways that create a sketchy ambiguity as to whether the path applies to the inner DID or the outer DID. the only DID method that takes other DID as an element that I can think of is did:did, which was literally an april fool’s joke.

more practically, since AFAIK no one designed a DID method with the use-case of being wrapped in another DID method in mind, it feels like a lot of unwanted side-effects and behaviors would fall out from that… if I can’t talk you out of defining and registering the first “envelope DID method”, might I recommend that in the method spec, you explicitly specify how parsers should handle composition of did:ap with did:cheqd or other path-enabled methods?


Update: #283 - FEP-ef61: Update proposal - fediverse/fep - Codeberg.org

@bumblefudge I added did:ap resolution algorithm and a note about ap:// scheme.

1 Like

Just a few small remarks:

Semantically, for the actor ID, shouldn’t it just be the “authority-part” of the DID, rather than having a /actor path added? The base DID itself should be resolvable to a DID document that represents the DID identity, and it’s kind of like a catch-22 if you have objects that are grouped under the DID, where the identity itself is grouped under the DID. It also adds ambiguity, unless everyone must explicitly use /actor for the actor object.

As for swapping did:apkey to did:ap:key, and using did:ap like a general namespace: I’m not sure if that’ll cause confusion when officially submitting the DID method for inclusion in the DID method registry, given did:ap wouldn’t be a complete DID method itself, and I don’t know if there are any accepted entries that treat the top-level as a generic namespace for “submethods” instead.

And as I mentioned in the other thread: it might not be worth distinguishing a did:ap counterpart for did:web, as just another application route can be added to handle application/did+ld+json which might possibly be able to even return the same content.

DID represents an identity. /actor is a file in a distributed filesystem, owned by that identity. FEP-ef61 establishes a clear separation of concerns and enables composability (did:key can be replaced by any other DID method). The path to actor object could be different, /actor is just an example.

I don’t think so. In ActivityPub everything is an object, including actors, this principle still holds in FEP-ef61.

Then did:ap will be the first one (unless the whole idea of portable objects somehow turns out to be unworkable).

Do you want to create a new did:web for every object? Otherwise, how non-actor objects will be identified? IIRC did:web doesn’t support DID URLs

I’m thinking about moving the definition of aliases property into a separate FEP, because it can be used in other scenarios (examples: 1, 2).

Pre-draft feps/d2da/fep-d2da.md at main - silverpill/feps - Codeberg.org

I guess in an alternative discussion angle: what is being gained with did:web or did:ap:web over essentially being a DID-style re-flavoring of WebFinger or just plain URLs? Is it explicitly prohibited to have a username-less query in WebFinger (such as to function as a “domain-based” handle)?

On other discussion points: what is the benefit of using a static .well-known address for the DID resolver, instead of the conventional “follow your nose” (pattern?) of ActivityPub, such as perhaps instead defining DID resolvers as a list in the endpoints property? I think even the DID core spec lets you use any URL structure for a DID resolver, rather than recommending a hard-coded path. For example, how favorable would the following suggestion below be (some properties skipped for brevity)?

  "@context": [
  "id": "https://example.social/did/did:ap:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2",
  "type": "Person",
  "aliases": [
  "preferredUsername": "alice",
  "endpoints": {
    "sharedInbox": "https://example.social/sharedInbox",
    "didResolvers": [
  "inbox": "https://example.social/did/did:ap:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/inbox",
  "outbox": "https://example.social/did/did:ap:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/outbox",
  "followers": "https://example.social/did/did:ap:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/followers",
  "following": "https://example.social/did/did:ap:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/following",
  "proof": {
    "type": "DataIntegrityProof",
    "cryptosuite": "eddsa-jcs-2022",
    "created": "2023-03-28T07:22:00Z",
    "verificationMethod": "did:ap:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2",
    "proofPurpose": "assertionMethod",
    "proofValue": "..."
  • aliases endorses the specific did:ap:key identifier.
  • didResolvers provides the client (and servers) the information for how/where to resolve the DID (and for the client to use for querying resources elsewhere, if authentication-gated), as any supported DID-based identifiers are appended to the recommended endpoints for resolution.
  • Trim the recommended didResolvers strings from the beginning of the actor ID, and beginning of other URLs listed in aliases, which should result in the plain DID, to imply to DID-aware ActivityPub servers that the resolver-based URL ID is just an alias for the DID.
  • The identity proof covers the did:ap:key listed in aliases, which matches with verificationMethod, so the identity is reciprocally self-endorsing.

To iterate further on the ID de-duplication:

Trimming https://example.social/did/ or https://fallback.example/resolve/ (whichever matches the beginning) from an ID, such as https://example.com/did/did:ap:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2 would result in an output of did:ap:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2 which matches the DID listed in aliases, attesting they reference the same thing.

Meanwhile, further on the explicit /.well-known/apresolver path: it only incurs more limitations on implementers, while probably not gaining anything, while the actor’s server domain still has to be known anyway to make use of the /.well-known/apresolver path to begin with, and also limits implementers from choosing shorter options.

(Sorry if there’s anything I’m asking that I’ve already asked before, or discussion I’ve overlooked, as I tend to be a bit of an amnesiac sometimes)

1 Like

It depends on the resource URI scheme. The acct scheme used for Mastodon-ish federation requires at least one character for the URI userpart. However, one could use another URI scheme like [dns](https://www.rfc-editor.org/rfc/rfc4501) (e.g. dns:mydomain.social) that doesn’t have this requirement (or did, https, urn, …).

1 Like