w3c-ccg / did-method-key

DID (Decentralized Identifier) did:key method for embedding keys in DID urls

Home Page:https://w3c-ccg.github.io/did-method-key

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Architecture Issues

OR13 opened this issue · comments

While adding support for ed25519, x25519, secp256k1, bls12381, p384 here: https://github.com/transmute-industries/did-key.js

I've encountered a number of structural issues related to the architecture of "LD KeyPair" Class, "Multicodec Fingerprints", "Signature Formats", and. "Key Transformations".

I'm creating this "main" ticket to document them in one place, hopefully we can close it out once we split them up / address them.

DID Key KeyPair Classes rely on this static method of key classes:

 static fromFingerprint({ fingerprint }: any) {
    // skip leading `z` that indicates base58 encoding
    const buffer = bs58.decode(fingerprint.substr(1));
    // https://github.com/multiformats/multicodec/blob/master/table.csv#L77
    if (buffer[0] === 0xe7 && buffer[1] === 0x01) {
      const publicKeyBase58 = bs58.encode(buffer.slice(2));
      const did = `did:key:${Secp256k1KeyPair.fingerprintFromPublicKey({
        publicKeyBase58,
      })}`;
      const keyId = `#${Secp256k1KeyPair.fingerprintFromPublicKey({
        publicKeyBase58,
      })}`;
      return new Secp256k1KeyPair({
        id: keyId,
        controller: did,
        publicKeyBase58,
      });
    }
    throw new Error(`Unsupported Fingerprint Type: ${fingerprint}`);
  }

At resolution time, this same method is used to construct the did document from the fingerprint.

From an information theoretic perspective... the only thing needed by DID Key is a spec and a multicodec fingerprint.

The DID Document is deterministic.... did:meme exploits this, to only store the fragment of did key... like so: https://github.com/OR13/didme.me/blob/master/src/core/index.js#L42

What this means is that the choice of publicKeyBase58 for encoding key bytes is pretty much irrelevant... the fragment is all that is needed....

Additionally, many LD Key Pair classes use internal representations that look almost indistinguishable from verification methods...but they contain private keys!

For example:

{
  "ed25519KeyPair": {
    "type": "Ed25519VerificationKey2018",
    "id": "#z6MkoWo8uK9v5nHPysMDn6CNXKsEKVqZwqfoxAQ4fkL6U6Hn",
    "controller": "did:key:z6MkoWo8uK9v5nHPysMDn6CNXKsEKVqZwqfoxAQ4fkL6U6Hn",
    "publicKeyBase58": "A4Y6K4uUkEnvsNWX6XEXgEKEVvZiXxRTG9V8qUN5YsWQ",
    "privateKeyBase58": "5WdcKfES92pvKQb8kpLgydbBBhosWKM7wdfN7hQGqBhmET6jXyVLPitXihnHd4Gn4jiJMqPxDMcvyoAM6wstykZn"
  },
  "x25519KeyPair": {
    "type": "X25519KeyAgreementKey2019",
    "id": "#z6LSfrzXx8Qm3kKofvV97CibKJazM8xE6X8pe186JL6nzNZX",
    "controller": "did:key:z6MkoWo8uK9v5nHPysMDn6CNXKsEKVqZwqfoxAQ4fkL6U6Hn",
    "publicKeyBase58": "5BpNRpbtxHc4aY7NaZCdziNWVzR7Puxfm2QQosTGGznm",
    "privateKeyBase58": "5KcPE6GFc3vaaaU8ntoBziWCVHckfAL5QjaBBW9mDFM9"
  }
}

I suggest we stop doing this, and we start using internal representations that use Buffers / UInt8Arrays... and we rely on explicit serialization methods for external representations... for example:

let key = await Ed25519KeyPair.generate()
const vm1 = key.toVerificationMethod('base58btc');
const vm2 = key.toVerificationMethod('jwk');
const publicKeyJwk = key.toJwk();
const exportPrivateKey = true;
const privateKeyJwk = key.toJwk(exportPrivateKey); /

See https://github.com/digitalbazaar/crypto-ld/blob/master/lib/LDKeyPair.js#L75 for something pretty close...

did:key is a linked data format, and uses JSON-LD...

I suggest we use the more compact representations everywhere @base + fragment ids...

I suggest we register a did parameter for asking for a did:key with all verificationMethod types JsonWebKey2020 so that web crypto is sufficient for implementations and so that software isolated browser keys / hardware isolated enterprise KMS can be used.

I suggest we decouple sign and verify from keypair classes....

secp256k1 for example, theoretically needs to support all of the following:

ecdsa
schnorr
ES256K (JWS)
SS256K (JWS)
ES256K (Detached JWS)
SS256K (Detached JWS)

A (private) key is really an input to a signing algorithm... not an instance of class which has a private member and public method that relies on it to support signing... the same is also true of "Key Agreement" keys... like x25519 keys.... typed functional programming is generally safer than object oriented programing

Good software implementations of did key should have clear types, classes, interfaces and functions, and should focus on functional programming first and then composition over inheritance second... https://en.wikipedia.org/wiki/Composition_over_inheritance

For an example of this, here is a bug in our ed25519-signature-2018 implementation, that arose from "assuming" the signature...

https://github.com/transmute-industries/vc.js/pull/9/files#diff-6195d357a62a5cd1b451f8f59676046bR277

Ed25519Signature2018 should really have been named Ed25519DetachedJsonWebSignature2018... since it does not actually use vanilla EdDSA....

In order to make the verificationMethod smaller on LD Proofs, we could make did:key use a convention like this:

did:key:fingerprint#0... since repeating the fingerprint is redundant... and since the did document structure cannot be updated.. the fragment values are safe to minimize.

What is the difference between DID Key KeyPair and an LDKeyPair?.... a DID Key KeyPair IS an LDKeyPair with multicodec derived values for id and controller... in other words... it's just a set of default values when none are provided....

I think we have a real opportunity with did:key to establish a set of common interfaces for linked data keys....

@dlongley @dmitrizagidulin @msporny What do you think about defining LDKeyPair Class and DIDKeyPair Class formally in this CCG work item?

also ping @tplooker who has a key class that sorta follows this convention... https://github.com/mattrglobal/bls12381-key-pair/blob/master/src/Bls12381G2KeyPair.ts

I suggest we stop doing this, and we start using internal representations that use Buffers / UInt8Arrays... and we rely on explicit serialization methods for external representations...

This sounds compelling.

I suggest we use the more compact representations everywhere @base + fragment ids...

I'm concerned about this one, using a JSON-LD feature that was meant to be rarely used to get some size compression when LZ compression will typically give us this optimization. Why are you seeking "the more compact representation"?

What do you think about defining LDKeyPair Class and DIDKeyPair Class formally in this CCG work item?

LDKeyPair might be better to do in the LD Proofs / LD Signatures specs. I'm fine w/ defining DIDKeyPair here, although I'm concerned about how the browser vendors might view that sort of definition in WebIDL (thinking a browser MUST implement it when what we're really doing is just establishing a formal implementation pattern. It might be viewed as "weird" by W3C folks, but maybe that's ok.

In defer to @dlongley and @dmitrizagidulin on the rest as they are the ones that did the implementation. @davidlehn might have some thoughts here as well.

I'm concerned about this one, using a JSON-LD feature that was meant to be rarely used to get some size compression when LZ compression will typically give us this optimization. Why are you seeking "the more compact representation"?

readability... as a developer, I am comparing strings... the shorter / less repetitive they are, the safer they are... for example:

https://github.com/transmute-industries/did-core/tree/master/packages/data-model#compact-representations

LDKeyPair might be better to do in the LD Proofs / LD Signatures specs. I'm fine w/ defining DIDKeyPair here, although I'm concerned about how the browser vendors might view that sort of definition in WebIDL (thinking a browser MUST implement it when what we're really doing is just establishing a formal implementation pattern. It might be viewed as "weird" by W3C folks, but maybe that's ok.

The main goal would be to define the typed interfaces we are using... end of the day... we are creating an API which might make its way into web crypto eventually.... we should design it carefully... and it would be easier to coordinate on interfaces and types if they exists outside of code... DIDKeyPair seems like an easy trial run of this, and we would not need to create a new work item, we can just add it to the spec, and later, pull it out when there is a better home for a generalization of it.

the shorter / less repetitive they are, the safer they are.

I'd suggest that the code passes in the base URL via the API w/o it being set by JSON-LD. Overriding the base URL is usually an approach of last resort. Even putting spec text in the DID Core spec that says that the BASE URL must be set to the id of the DID Document would be more preferable than hard coding @base in JSON-LD. The DID Core spec text approach would make the solution work in non-JSON-LD DID Doc representations.

we are creating an API which might make its way into web crypto eventually

I hope not... the Web Platform doesn't need more native browser code... ideally, it would be reduced over the years. Like tearing out native RDF/XML support from Firefox. :)

we would not need to create a new work item, we can just add it to the spec, and later, pull it out when there is a better home for a generalization of it.

+1, happy to take that approach for now.

I feel we have mostly moved this discussion to:

https://github.com/OR13/best-linked-data-suites-2020
transmute-industries/vc.js#13

I'm going to close, we should reopen an smaller related issues separately so they are more actionable.