Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should we represent service block info as a DID with initial-state matrix params? #33

Closed
TelegramSam opened this issue Feb 28, 2020 · 30 comments

Comments

@TelegramSam
Copy link
Collaborator

Per discussion on Slack, it has been suggested that we leverage the initial-state matrix parameter to represent service block info when unattached to a DID. This is common both in the invitation stage of exchanging DIDs and with ephemeral challenges, where endpoint, encryption keys, and routing keys are passed.

Each DID method can define how initial-state values apply to the method. did:peer could use this, and we might also work this into did:key to attach the additional needed information. As the method defines use, we can design this to be concise.

Thoughts:

  • initial-state feels young, even to the point of renaming what the name will be. Matrix parameters are also under active discussion. Using the same approach may provide external pain as we adapt to changing external decisions.
  • Using this approach means that the value is always a DID and may be passed to a library for DID method based evaluation. This provides a consistent dev experience between resolved DIDs and information passed inline (like did:key+service info).
@dhh1128
Copy link
Contributor

dhh1128 commented Feb 28, 2020

I'm just leaving a comment so I'm subscribed to this issue. (Clicking "subscribe" in github hasn't been working for me.)

@OR13
Copy link
Contributor

OR13 commented Feb 28, 2020

Can someone with more experience using did:peer describe in concrete terms what the KERI inception event like thing is that happens when a did:peer is first created?

I have a feeling understanding that is the key to this issue.

In sidetree the initial-state is an unwitnessed inception event, which is acceptable only when no witnessed inception event is observable on the ledger.

@csuwildcat
Copy link
Member

In sidetree the initial-state is an unwitnessed inception event, which is acceptable only when no witnessed inception event is observable on the ledger.

I like how you put this, that's a great way to word it!

@OR13
Copy link
Contributor

OR13 commented Feb 28, 2020

@SmithSamuelM ^ We have a couple conversations floating around related to this, which I would love your thoughts on.

@dhh1128
Copy link
Contributor

dhh1128 commented Feb 28, 2020

I'll answer Orie's question for peer DIDs specifically. Then I'll make some observations about principles of self-certifying identifiers that @SmithSamuelM has been talking about. Sam's principles are embodied in did:peer, but not as purely as the way Sam advocates (peer DIDs mix in a couple other concerns). Sam has done a beautiful job of abstracting some ideas further than peer DID does.

A peer DID's state is just a bunch of signed deltas to a DID doc. The first delta is between null state and the genesis state of the DID doc. The genesis state of the DID doc must include one key that is special -- the inception key. This is the key that authorizes the DID to be shared, either with a blockchain or with a peer., which means that the inception key must sign the first delta. The value of the DID itself (its "numeric basis") has to be provably derived from the genesis state, including the inception key. If someone tries to register the DID (either with a peer or a blockchain) using any key other than the inception key, they are doing something invalid, no matter what other privileges that key has. Requiring that only the creator's inception key can authorize the initial state prevents various attacks. (There's more to this story, including how registrations for non-genesis state can happen, but let's set that aside for now.)

Now, peer DIDs are exchanged between peers using a message. This message could be part of the Aries Connection or DID Exchange protocol, or it could be just the payload of an HTTP POST that has nothing to do with DIDComm. The peer DID spec contemplates both. Either way, the message must represent the state of the other party byte-for-byte, not just a semantic equivalence of it. That's because what's signed is the byte-for-byte genesis state, and it's this signature that's tested to make sure the peer DID has the self-certifying property (inception key authorizing). We are not doing any JSON normalization in the style of LD-Signatures.

If I understand the proposal correctly, we want to stop attaching genesis DID docs to messages, and instead simply tell the other party "here's my initial state" using the initial-state matrix parameter. If the state in question is the initial state of a peer DID, then what we would be encoding in the matrix parameter is a binary representation of the full text of a peer's DID document. As long as initial-state is capable of holding such a thing, the idea is probably viable, though we could easily be talking about 1k-2k of data. What it cannot be is a normalized version of a DID doc, or just a few values for a DID doc that's derivable from those values like did:key; these would invalidate our signature test.

@dhh1128
Copy link
Contributor

dhh1128 commented Feb 28, 2020

Sam Smith's ideas about KERI and self-certification basically genericize peer DIDs. Where peer DIDs assumes that all changes are authorized and propagated and witnessed by peer agents, KERI assumes a similar concept, but allows the participants to be anything the parties like -- peer agents, blockchains, or other systems of record. They are thus blockchain agnostic and portable.

KERI also simplifies some stuff out of peer DIDs. A peer DID allows initial state to be complex; KERI requires it to be simple. This makes a KERI impl of peer DID concepts far closer to did:key; the amount of material you have to transfer might be much less than the 1k-2k for peer DIDs, and closer to 100-200 bytes.

I am going to bring the peer DID method to the DIF's WG that's looking at KERI, and ask that it be rolled in to the work agenda. I think the result will be a reframing of peer DIDs, explaining them as a special case of KERI.

@SmithSamuelM
Copy link

@dhh1128 @OR13 Daniel’s comments don’t a good job of summarizing the similarities of KERI and peer dids. From a design perspective KERI is meant to be the minimalist set of key events primitives upon which any DKMI can be built using best practices. Thus one could refactor peer dids to use KERI plus some extensions

@OR13
Copy link
Contributor

OR13 commented Feb 28, 2020

@dhh1128 thanks this is super helpful. I've also be advocating for considering sidetree as a case of KERI as well, with the ledger as the witness.

The genesis state of the DID doc must include one key that is special -- the inception key. This is the key that authorizes the DID to be shared, either with a blockchain or with a peer., which means that the inception key must sign the first delta.

So in a world where the inception key were a did:key.. you could start with a did:key, and end with a peer did. I assume that a key rotation of the inception key is possible, you would no longer need to retain the did:key (its not revocable, so you don't want to use it long term anyway).

The problem with did:key is that it does not have a services section... which is necessary to support the initial invitation flows used by didcomm IIRC.

If we found a way to use initial-state or something similar to augment a did key, you would have everything you need to covert that identifier to a peer did, and it would be globally resolvable.

I think the flow would be as follows:

  1. Entropy -> Private Key -> Public Key
  2. Public Key -> did:key
  3. did:key + initial-state / injected serviceEndpoint
  4. invitation request.
  5. reply from did:peer

You could skip the did:key part, but I'd like to see it there because I think it is likely to help with adoption of dids generally...

initial-state is feeling not generic enough... what we are really wanting to do is pass some attributes which are signed by a did, along with that did as matrix parameters... a method resolver that supports these matrix parameters will use them to augment the resolution process.

In the case of sidetree, it tries to resolve the did, and then uses the initial-state to construct the did and did document if none exists... this allows us to transmit the inception event as a did uri.

In the case of did key, it might add a service endoint to a did document, if it were signed properly... this would allow for did key to be used as an ephemeral globally resolvable did, with the serviceEndpoint mechanism as a way to upgrade to a longer term did... and with key agreement section, you could make sure all communication was encrypted from the start.

In the case of did peer, if we had an ephemeral globally resolvable did, like the example above, we could reduce the attachment of the did document to the use of a did uri... because the peer did would now be resolvable globally.

This would simplify the didcomm protocol, by removing the need to handle special cases for did:peer.

@csuwildcat
Copy link
Member

@OR13 the way you pose that, it does sound like initial-values would be the more generic term/concept to go with, vs mentioning 'state'.

@dhh1128
Copy link
Contributor

dhh1128 commented Feb 28, 2020

@OR13 : I followed most of what you said, and it made sense to me. But then your comment near the end threw me for a loop. I think I'm missing something, so I'm hoping you'll elaborate. You said:

"...because the peer did would now be resolvable globally."

The whole point of peer DIDs is that they should NOT be resolvable globally. That what "peer" in the name means. They're only supposed to be resolvable by each other. So I'm a bit puzzled as to why making them resolvable globally is a good thing.

@OR13
Copy link
Contributor

OR13 commented Feb 28, 2020

Having did:peer not be globally resolvable is IMO, a defect and not a feature.... Since we know that the inception event for a did:peer is a function of a publicKey and a serviceEndpoint... we could use initial-state to make the did resolvable, it would only be resolvable by parties which held a did uri akin to the sidetree inception event:

did:peer:<id>;initial-state=<signed_first_delta>... now your did peer is resolvable by anyone who has this... but its still private... as long as you share this over a secure channel you are in the same place we are today with passing a key and service endpoint....

Same thing could apply to did:key and if did:key supported initial-state or something similar: digitalbazaar/did-method-key#5

@dhh1128
Copy link
Contributor

dhh1128 commented Feb 28, 2020

Having did:peer not be globally resolvable is IMO, a defect and not a feature....

I can't NOT comment on a statement that provocative. :-) However, the next paragraph can be ignored for the purpose of this thread, so if you want to skip the controversy and get to what matters, jump past this next part...

Alice is the one who decides whether resolvability of her identifier is a bug or a feature. If she creates an identifier to use only with Bob, the rest of the world can have whatever opinion it likes about whether Alice's DID with Bob should resolvable for them -- but only Alice's opinion matters. It's her DID, after all. Having a DID like this be publicly resolvable is a little bit like two lovers saying, "Hey, let's invent pet names for each other that only we know. But then let's go and list them in a public registry so the whole world can know what they are. You know, 'cause names are meant to be public." I think that's silly -- not just for privacy reasons, but because it's totally unnecessary to construct systems that support this. Nobody's asking for it. Of course we want resolvability for DIDs that are intended to be public. But peer DIDs aren't intended to be public, by definition. Where I really think we have a disconnect is on whether most DIDs should be public or private, and on whether we believe discoverability, routability, and resolvability are functional synonyms. I believe the vast majority of relationships that will ever exist are non-public ones -- not because I'm hung up on privacy, but because I think it's just reality that the world doesn't care about them and shouldn't be burdened with them. Anyway, we should set aside some time to talk about this face to face; we won't resolve this in an isolated github issue.

Back to the main topic: you said "as long as you share this over a secure channel you are in the same place we are today with passing a key and service endpoint...." I can agree with this, which is why I think this mechanism would work. I don't actually think it de-complicates DIDComm at all, because the mechanism for attaching content is generic to all DIDComm; its cost in the connection or DID Exchange protocol is essentially zero. But I don't mind going this direction for other reasons (like de-complicating the exchange of DIDs over non-DIDComm channels).

@kdenhartog
Copy link
Contributor

kdenhartog commented Mar 2, 2020

What it cannot be is a normalized version of a DID doc, or just a few values for a DID doc that's derivable from those values like did:key; these would invalidate our signature test.

Can you explain why this is true a bit more? An example would be really helpful I think. From what I’ve read in KERI and sidetree making ‘initial-value‘ normalized wouldn’t change anything so I must be missing the core point here.

@csuwildcat
Copy link
Member

@kdenhartog I had the same question when reading that, because initial-values doesn't care, and you should feel free to construct your DID Method's initial values data in whatever way securely maps those values to the DID they are attached to.

@csuwildcat
Copy link
Member

I believe the vast majority of relationships that will ever exist are non-public ones.

I actually agree that the average user's number of private DIDs will outnumber their public ones by a ratio of hundreds or thousands to one, but here's where I differ: the vast majority of data traffic/volume will occur via the 1 or 2 public DIDs a user has. This is because those 1 or 2 public DIDs will be the ones used to represent a user's public persona, which spans across social media, professional networks, and other intended-public forums. I believe intended-public communications from public persona DIDs will dwarf all other types of DID communications by orders of magnitude, as evidenced by the volume of intended-public traffic that currently runs across apps like Twitter, LinkedIn, Facebook, Yelp, Pinterest, Stack Overflow, Medium, YouTube, etc.

@dhh1128
Copy link
Contributor

dhh1128 commented Mar 2, 2020

@csuwildcat : I'll reply to the bulk-of-communications-being-public issue on the DIDComm channel on slack, since it's a tangent to this github issue.

@TelegramSam
Copy link
Collaborator Author

What it cannot be is a normalized version of a DID doc, or just a few values for a DID doc that's derivable from those values like did:key; these would invalidate our signature test.

Can you explain why this is true a bit more? An example would be really helpful I think. From what I’ve read in KERI and sidetree making ‘initial-value‘ normalized wouldn’t change anything so I must be missing the core point here.

This has to do with signatures. JSON is hard to canonicalize. Forcing the representation to be bytes makes it possible to sign and validate.

@TelegramSam
Copy link
Collaborator Author

Alice is the one who decides whether resolvability of her identifier is a bug or a feature.

I think we are struggling with the meaning of globally resolvable. I think Alice should be the one who decides as described by Daniel. Perhaps the term we are looking for is something like globally usable or globally distributable.

@awoie
Copy link
Member

awoie commented Mar 2, 2020

I believe even did:ethr could benefit from initial-state. By doing so, we could add the key agreement key and default service endpoint to the initial DID Doc and bypass the Ethereum transaction problem. If we need to update these values at a later point we would use our ERC1056 smart contract for this. Anyways, supporting initial-state would increase privacy (by avoiding potential PII on the Blockchain) which is why I sympathise with this proposal.

@OR13 What prevents me to have different initial-state with other peers in case I decide to reuse my did:ethr with other connections? Would this be a non-issue (except correlation of course)?

@OR13
Copy link
Contributor

OR13 commented Mar 3, 2020

There were more comments related to this on: w3c-ccg/did-method-key#5

I think if this is generalized properly, it will allow privacy features for did methods that rely on ledgers, and usability features for methods like did:peer and did:key...

I think its likely to be up to the method implementer what to support, so while the mechanism might be generic, the method implementer would still be responsible for deciding if you can add a phone number to did document using matrix parameters.

@OR13
Copy link
Contributor

OR13 commented Mar 3, 2020

@OR13 What prevents me to have different initial-state with other peers in case I decide to reuse my did:ethr with other connections? Would this be a non-issue (except correlation of course)?

@awoie To answer this directly... I think its a non-issue / feature, but you can certainly combine this approach with did:peer or some HD-Wallet scheme to get better privacy features.... you could also choose to embed different key agreement keys for different parties.

For example, you might inject controller information that provides for correlation, such that the ledger does not have the correlation details but the did uris do.

@kdenhartog
Copy link
Contributor

@dhh1128 when you say normalize do you mean the same thing as canonicalize or something different? As I understand sidetree today, the base64url encoding of the did doc that's passed with the initial value parameter would satisfy this requirement because it's a method of canonicalizing.

What I understood from the comment was that normalization (I'm assuming it means the same as canonicalization) would mess things up.

What it cannot be is a normalized version of a DID doc

This is the part that through me off. In my mind it MUST be a normalized (canonicalized) version of the DID doc.

With that in mind, I think the tricky part would be keeping the b64url form so that it can remain self certifying later. However that's more an implementation detail that doesn't prevent things from moving forward afaik.

@dhh1128
Copy link
Contributor

dhh1128 commented Mar 9, 2020

Yes, I mean "canonicalized" when I said "normalized."

The peer DID method requires peers to receive the bytes exactly as the other peers create them, and imposes no canonicalization requirement. Thus, we can't send DID docs unless we transmit them exactly as the other part sent them.

@TelegramSam
Copy link
Collaborator Author

Discussed at length in the 2020-March-09 WG Call, with the following summary:

  • We can use initial-state to convey the necessary items for the Peer DID spec, either as a matrix param or as a query param (in the event that matrix params don't make it into the spec)
  • We can use either did:key with endpoint information passed in initial-state, or did:peer with the same, to accomplish ephemeral use cases.
  • Given the above items, we can eliminate the did_doc attributes in the request and response messages in the did exchange protocol.

@llorllale
Copy link
Contributor

Ended up here looking for an answer to: *how can I create an OOB invitation without incurring the costs of creating a public DID?".

For reference:

  • Here is the OOB invitation format. Notice we're missing the service array from didcomm v1, hence no way to specify an explicit invitation
  • The DIDComm JWM Profile mandates that from be a valid DID.
  • The JWM verbiage's link is outdated and still pointing to the #generic-did-syntax section which is today just #did-syntax.
  • The did-core spec's generic syntax does not include fragments nor query parameters.

Problem 1: the DIDComm JWM profile mandates that from be a valid DID that satisfies the did syntax... which prohibits us the use of initial-state or any other parameter or fragment.

But let's assume that we can put initial-state in from....

The only feasible way I see this happening is - as described in this thread - put a did:key with initial-state and then immediately rotate or "upgrade" to a did:peer using from_prior.

Ok. Now I look up the sparse specification for the did:key method and see no generic algorithm to "create" a did:key doc from any key type... keeping in mind we need to keep encryption keys separate from signing keys as best practice...

Problem 2: the did:key method is under-specified. Eg. it's not clear to me how, according to the spec, one can inflate a did doc from a P-256 key?

@llorllale
Copy link
Contributor

Of course, problem 2 goes away if I just use a did:peer + initial-state.

@OR13
Copy link
Contributor

OR13 commented Aug 26, 2020

w3c-ccg/did-method-key#16
w3c-ccg/did-method-key#17

https://github.com/transmute-industries/did-key.js/blob/master/packages/ed25519/src/Ed25519KeyPair.ts#L196

In order to get a JWK from a did key, you only need to know the DID... and be clever enough to convert multicodec to JWK...

I personally prefer the use of multicodec to what KERI is doing reinventing it slowly with base64url and a custom table mapping....

The next version of did peer if based on KERI, will be capable of doing the same thing however, they just won't be able to use off the shelf libraries like multiformats/js-multicodec#64 ...

@llorllale
Copy link
Contributor

Discussed on today's call:

  • initial-state was excluded from did-core
  • did-core opted to embed the doc's state as a segment in the identifier
  • @OR13 proposed signed JSON patches (query param signed-ietf-json-patch)
  • Possibility of allowing from and from_prior to be DID URLs (not just DIDs) and prohibit fragments

@llorllale
Copy link
Contributor

llorllale commented Nov 2, 2020

from - OPTIONAL. Sender identifier. The from attribute MUST be a string that is a valid DID which identifies the sender of the message. The from field MUST be included if authentication of the DIDComm message is needed. See the message authentication section for additional details.

If a DID doc has multiple keyAgreement keys, how to specify which one? Requires a DID URL. from is at the inner message layer, not at the outer encryption layer.

@TelegramSam
Copy link
Collaborator Author

Close in WG Meeting 2020-11-02

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants