aries-rfcs
aries-rfcs copied to clipboard
RFC 0056 & RFC 0023 Usage of inline public keys
A reoccurring issue has been how to sufficiently represent inline keys in a concise syntax whilst preserving the required information around a public keys encoding and underlying type.
The did:key method appears to solve these issues by leveraging the following list of multicodecs.
Using this method we could replace any current references we currently have to inline public keys that at present do not include any information about the public keys encoding or type.
An example of this in the service decorator instance would be the following.
{
"@type": "somemessagetype",
"~service": {
"recipientKeys": ["B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u"],
"routingKeys": ["B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u"]
"serviceEndpoint": "https://example.com/endpoint"
}
}
Would change to
{
"@type": "somemessagetype",
"~service": {
"recipientKeys": ["did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"],
"routingKeys": ["did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"]
"serviceEndpoint": "https://example.com/endpoint"
}
}
Whereby resolvingdid:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH
would yield the following
{
"@context": "https://w3id.org/did/v1",
"id": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
"publicKey": [
{
"id": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
"publicKeyBase58": "B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u"
}
]
//Further information from the did doc omitted
}
Hence yeilding the underlying public key of B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u
, its encoding of base58
in this case and the type of key it is Ed25519VerificationKey2018
@dhh1128 @kdenhartog @TelegramSam
For the time being, I am STRONGLY opposed to introducing a dependency on did:key. I don't agree with any of the extra semantics that this method wants to introduce -- specifically the ones related to assertionMethod
, capabilityDelegation
, capabilityInvocation
, and keyAgreement
. This is because, while I love the OCAP model, the DB method is linked to an implementation of OCAP (OCAP-LD) which I consider dangerous and unnecessary; Sovrin-style credentials are a safer, cheaper, and more privacy-respecting OCAP solution because of the way they test the delegation chain and revocation properties. Now, I am not trying to argue the OCAP issue in this ticket, and it's entirely possible that we could converge over time--but I don't want to jump on the bandwagon of this DID method and accept a bunch of assumptions that we later have to unwind.
However, I think the key encoding mechanism is clever and useful. So why don't we do what you are proposing, except not use the did:key:
prefix. That is, just provide the keys in encoded form, such that if we decide to use did:key later, we can--and if we don't, we haven't lost anything because we still have the property we truly need.
I agree with Daniel. The encoding is very useful, but the highly opinionated derived DIDDoc is asking for trouble. If there was a way to define our own DIDDoc template, that would be helpful, but that not be did:key
. I think that without explaining to developers the purpose of all the fields in the generated DIDDoc (using phrases other than "ignore this"), we're asking for trouble.
I do like the concise encoding.
@dhh1128 point taken, perhaps more education and understanding of some of the implications that this method entails is required. Are you prepared to document some of your concerns so we can evaluate, alternatively I'm happy to jump on a call to discuss further, or perhaps we can put this on the agenda for the WG call? To me inventing a new syntax that has no prefix feels like wasting/duplicating alot of the work of the DID spec which is to establish a uniform data model behind these style of identifiers.
Tagging #104 explicitly because of its overlap I didn't see until I opened this issue
inventing a new syntax that has no prefix feels like wasting/duplicating alot of the work of the DID spec
Actually, there is a prefix on the key value in my proposal--the one given by multicodec. I'm proposing that we use it exactly the way it was designed--as a prefix for keys. What I'm proposing to jettison is the did:key
prefix, which is NOT "work of the DID spec" but of a specific DID method that is brand new to the public eye. Using did:key
as a prefix for keys (as opposed to DIDs) is a nonstandard way to encode keys that would ignore the the DID spec -- not the other way around.
Re. concerns:
First concern: the items we're listing in ~service.recipientKeys
and ~service.routingKeys
are keys, not DIDs. Putting DIDs where keys belong is an error. (Yes, I know that in did:key, a key's id
and the DID's id
are identical by design. I think that's a conceptual problem, because it gives two nodes in the same JSON that have the same id
value; go look at the sample DID docs. Now you need specialized parsing logic to disambiguate. Even if that's more of an annoyance than a problem, we can't hard-code into DIDComm an odd assumption about DID~key equivalence that happens to only be true of one DID method.)
Second concern: The OCAP solution that DB is advocating is, if I understand correctly from conversations with Manu, an evolution of ZCAP-LD. Manu told me in our last conversation a few weeks ago that the current spec is stale. However, it seems clear from reading it that an object capability is going to be validated by traversing a chain. If I have a capability that's delegated and/or attenuated from A to E (A -> B -> C -> D -> E), then I have to walk the chain back to A to confirm the OCAP is still valid. This means every party in the delegation chain finds out when downstream usage occurs, which is a privacy problem--not to mention a fragility problem as it requires all parties in the chain to be available (or to have a queryable automated system available) whenever a delegation chain is validated.
Third concern: I'm concerned that using ZCAP-LD as did:key imagines externalizes a new dependency that's quite complex, at exactly the place where what we need is utter simplicity (raw keys). To know if someone is using their DID correctly, you now need a whole new document format (to represent your OCAP), a whole new validation mechanism (to parse the doc, traverse the chain, and consult with all holders of the doc about revocation), etc. The architectural dependency lines should go from simpler to more complex, with DIDs being near the bottom; now we're putting something beneath DIDs that's complex again--and we're doing it for the very simple set of use cases that you've advocated, where we don't want the complexity of establishing a connection yet. That's the wrong place to add complexity. (We could get around this by saying that none of the DIDs used in these ~service
decorators are allowed to have fancy delegation, but this would be inventing a subset of did:key
that's not in that spec.)
I have more concerns, too. We can certainly discuss on a community call.
I proposed this elsewhere but the main issue with bare public keys is that there is no explicit indication of the crypto suite type so a user does not know how to verify signatures. Merely including the crypto suite type as a DID URL query string is a simple way to provide this. Another approach would be to use a did matrix parameter. The latter can be universal but complicates the DID resolver the former would be on an application by application basis but does not encumber the resolver its merely a convention that in this case would be respected by DID Comm. This avoids creating a new DID method that is OCAP based.
A related discussion in the last couple of W3C DID meetings is where to put additional behavior for more types of verification. What tends to be happening is that proposers want to add functionality to the DID resolvers which is a death spiral by design, that is, as DID resolvers become more complex they get slower and more prone to failure and exploit this makes resolution increasingly expensive in general even for applications which do not need the additional behavior which induces more caching and other workarounds to avoid resolution etc etc. The suggested solution is to keep resolvers simple and provide additional verification through service endpoints that are specific to the additional behaviors. The root of trust for a DDo is the authentication block in the DDo. Changes to the DDo are governed by the DID CRUD methods. Thus any service endpoints are authN authZ by the crud methods and the Auth block. This means that there is little need to add anything to the DDo that the DID resolvers needs to do besides Auth and CRUD. One can other make their DID methods more complex or add service endpoints with more complexity. Either work and are less problematic than adding additional behavior to the resolver. The latter means that fewer special purpose methods are needed just special purpose endpoints. This is a more scalable solution IMHO
DID resolution should be a lightweight indirection to some DID service endpoints where all the work is performed. As opposed to a heavy weight one stop shop.
Adding the auth block to a DID URL vis query parameters allows for ephemeral DIDs to avoid resolution entirely. Which is even more lightweight.
@SmithSamuelM: Let me propose a requirement that may make it obvious why I'm not going down the DID URL route.
It should be possible to use, in the
~service
key lists, keys that are not associated with any DID, anywhere, ever. For example, it should be possible to use a key that's part of my SSH config, but not involved in DID-land at all.
To be fair, this requirement has never been stated anywhere before, but neither has the opposite (that all keys will always be DID keys). And given the fact that we're talking about using this in places where we either do not yet have, or do not ever intend to have, a DID-based relationship, I think it's a reasonable one.
Given that requirement, can you see why I'm not going down the DID URL query string route? Multicodec does specify the crypto suite for the key, so we don't have the raw key interpretation problem you highlighted.
One weakness of the existing internet is DNS resolution. It’s slow buggy and insecure. We don’t want IMHO to repeat those mistakes.
Given multicodec provides the crypto suite than the query string is not needed. But being a did still allows some degree of future proofing. I guess my inclination is to make DID work well in these applications not add another identifier class. The core feature of a DID is that its a self-certifying identifier. A public key is not quit a self-certifying because it is not resolvable but requires additional context whereas a DID is resolvable. This is a powerful feature. One can always add context to make a public key have meaning, but the whole basis of what many call Identity Based Crypto is to leverage the idea that the crypto context is self-contained in the identifier. So I always ask myself the question is this a use case where there is crypto that could be identity based crypto but isn’t and if not my presumption is that it should be unless there is a really good reason why not and context provided elsewhere is actually a reason to change. One of the roots of security problems and complexity is providing the context of the crypto elsewhere. One place is better than two places.
It’s a philosophical orientation.
But it may be that I am way off base here if its legacy standards we are trying to support.
The way to get both speed and future proofing is to use caching. This can be done with identity based crypto by making identifier itself be the catche and then have the resolution happen when the cache expires. Thus an ephemeral DID could have the crypto suite and cache expiration be embedded in the query string so that that resolution only happens if the DID is still valid after the cache expiration. (Other semantics would have the DID only be valid for the cache and then resolution never occurs (truly ephemeral).
Anyway just providing some food for thought.
A ordered dict (key: value pairs, or an ordered tuple of pairs, or a URL with query are all functionally equivalent representations and can be used interchangeable as identifiers for identity based crypto. So making them all DIDs is merely adding resolvability for future proofing and indirection. In general those two features are very powerful and may come at little cost if employed wisely. So everywhere I see identifiers with key material expressed as any one of the three I recognize them as functionally equivalent so I ask the question could it be a DID otherwise.
Ordering makes the dict and tuple consistently6 hashable so they can be identifiers in their own right not merely encodable as a URL.
The semantics of control embedded in a ‘DID is the essential feature of self-certifiability so having only one semantic for self-certifiability makes composing more complex behaviors decomposable to fewer primitives. This is an essential complexity reduction technique.
Thanks, I do understand that the multi-codec identifier becomes the prefix, however I'm still not convinced with this approach. I understand that did:key
is still forming and hence is not a complete solution but I am wondering if this is at least an opportunity to collaborate on forming a generalized ephemeral did solution.
The core of my argument is what do we want to refer to other parties in the ecosystem as, keys or some more generalized type of identifier? Because supporting both seems like a strange fork. The solution in my mind is simple whenever you are talking about a key that belongs to a did doc, reference it via a did-url, because chances are if you are authenticating a message from or sending a message to, you will be speaking about them in the context of their did not key. If it is an ephemeral key you are authenticating a message from or sending a message to, why not construct an ephemeral did so that the way in which you identify subjects is uniform. Otherwise in parsing fields like recipientKeys
or routingKeys
you will have to always have a branch of logic that is looking for the did prefix passing it to a did resolver and another branch of logic that tests if its multi-codec public key and instead perform some piece wise logic that is non-standard and will return some implementation specific understanding of the public key. This duplication creates headaches wherever this functionality is required and I see little benefit to effectively creating another identifier to a public key.
Another important observation to note is although DID's were not designed to be entirely human memorable, they are in most cases human identifiable, which is an important distinction to make. Because if we invest in creating a multi-codec form with no human identifiable prefix like did:key
this will potentially be the source of much confusion, i.e how do I as a human disambiguate between my base64 public key that was spat out of some system versus my multi-codec public key, see an extract taken from the example refereed to above.
B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u
=> z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH
Or with did:key prefix it becomes far less ambiguous.
B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u
=> did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH
In regards to your second and third concerns raise in your points above, my understanding of the applications of OCAP's is different to what you describe, in particular how you describe validating attenuated delegation in an OCAP model. For instance, the delegation chain can be embedded into an OCAP rather than having to traverse a chain and the revocation check can be either be a call back to the original issuer or it could reference some global revocation infrastructure like what Sovrin provides. The point i'm trying to make is that is an implementation specific detail and I don't think did:key (but I may well be wrong) has a hard opinion on the exact implementation and even in the event it does, is there still an opportunity to discuss this as an issue with one possible usage of did:key rather than something fundamental to did:key?
@daniel. I am not supporting did:key I would rather have a true ephemeral DID. @tplooker proposed did:key. But my comments are that this is similar to ephemeral DID and I think we should have an ephemeral did not did:key
@dhh1128 to your point about SSH above, unless you are talking about putting raw public keys in the fields described which re-introduces the ambiguity we are trying to solve, you will still have to cast them to some other representation before using i.e a multi-codec base or something like did:key
@SmithSamuelM I am happy to explore a more pure ephemeral did too if we deem did:key is too opinionated about its usage, what I am opposed to is creating a new identifier class i.e just using the multi-codec prefix to a public key.
@tplooker. Yes I share the same concern. I resist creating new classes of identifiers when I think that a DID will do. DIDs are sufficiently flexible that we should spend some effort adapting them vs just creating non-DID identifiers that are almost DIDs.
The URI speci proved to be a very resilient and long lived specification because it had a Goldilocks amount of flexibility. I would like DIDs to follow suite.
Fundamentally I am more in agreement with @dhh1128 than not. As long as the multicodec includes the crypto suite it is a self contained identifier than can be used in general for encrypting did:comm. There is no need for it to be a DID. The question for me is could an ephemeral DID provide the same functionality but with additional features. The key differentiator would be that a truly ephemeral DID does not require resolution to a DID resolver. If it did then we add a performance issue. I don’t think its a layering or recursion problem unless did resolution READ is DID:COMM encrypted. I was not aware that that was a thing. But should that be true then an ephemeral only DID is required which is functially the same. The only advantage then being the commonality of syntax with a DID allows code reuse of tooling for parsing etc. So its not a strong difference.
In general there are multiple use cases where we use crypto as part of a bootstrap of capability. A true Ly ephemeral DID would define a common syntax and semantics for all these bootstrap functions. In that sense a truly ephemeral DID is a different kind of DID. We could call it something else but then it would be good to define a new identifier class that is still identity based crypto (aka self-certifying identifiers) but are completely self-contained and ephemeral. I like the idea of calling these ephemeral DIDs rather than defining a new name and class but I understand they are un DID like in important ways.
Attenuated Authorizations are a type of. Data not an identifier. They have an identifier but they are not the same as an identifier.
@SmithSamuelM correct in my mind with an ephemeral DID you would use a DID resolver that would just inflate the DID into a minimal DID Doc quoting one key, so no actually resolution in the traditional sense is required. Conceptually ephemeral DIDs are similar to did:peer in the fact they do not rely on a ledger, but they go one step further and remove the dependency on local state and maintaining local state. It is important to note I'm not raising this comparison to justify ephemeral DID's as a replacement to peer DID's, I think there are very valid use-cases for both, I'm merely pointing out that we already have an example of a did not anchored to a ledger.
Also creating another identifier standard is not a small undertaking, so we should approach this from the angle of why ephemeral did's do or do not fit under the did spec before we decide to create a new type of identifier, rather than the other way around.