From a9d5787b7eedb288d0a70e002a339a05083bf547 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 4 Aug 2022 14:58:20 -0400 Subject: [PATCH 01/44] Use eslint-config-db 4.1. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e454596..af831cf 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "chai": "^4.3.6", "cross-env": "^7.0.3", "eslint": "^8.16.0", - "eslint-config-digitalbazaar": "^3.0.0", + "eslint-config-digitalbazaar": "^4.1.0", "eslint-plugin-jsdoc": "^39.3.2", "eslint-plugin-unicorn": "^42.0.0", "karma": "^6.3.20", From de54f3cb406a54fe8fab7f093187d40d5f6112c9 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 4 Aug 2022 14:58:54 -0400 Subject: [PATCH 02/44] Add DidKeyError. --- lib/DidKeyError.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 lib/DidKeyError.js diff --git a/lib/DidKeyError.js b/lib/DidKeyError.js new file mode 100644 index 0000000..1100d42 --- /dev/null +++ b/lib/DidKeyError.js @@ -0,0 +1,18 @@ +/*! + * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. + */ + +/** + * Used to throw DidKeyErrors. + * + * @param {string} message - An error message. + * @param {string} code - An error code from the did:key spec. + * + */ +export class DidKeyError extends Error { + constructor(message, code) { + super(message); + this.name = 'DidKeyError'; + this.code = code; + } +} From 71c631bb3e4bb1d2803d9443a06f1c5f73a24835 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 4 Aug 2022 14:59:09 -0400 Subject: [PATCH 03/44] Start validator. --- lib/validator.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 lib/validator.js diff --git a/lib/validator.js b/lib/validator.js new file mode 100644 index 0000000..a723210 --- /dev/null +++ b/lib/validator.js @@ -0,0 +1,7 @@ +/*! + * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. + */ +import {DidKeyError} from './DidKeyError.js'; +export const validateDidKey = ({did}) => { + +}; From 890472b75fdca79b1df6edbf54668137e107a95c Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 4 Aug 2022 14:59:32 -0400 Subject: [PATCH 04/44] Sort imports alphabetically. --- lib/DidKeyDriver.js | 11 ++++++----- test/driver.spec.js | 11 +++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/DidKeyDriver.js b/lib/DidKeyDriver.js index 7e25886..b941810 100644 --- a/lib/DidKeyDriver.js +++ b/lib/DidKeyDriver.js @@ -1,17 +1,17 @@ /*! * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ +import * as didIo from '@digitalbazaar/did-io'; import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020'; -import { - X25519KeyAgreementKey2020 -} from '@digitalbazaar/x25519-key-agreement-key-2020'; +import {validateDidKey} from './validators.js'; import { X25519KeyAgreementKey2019 } from '@digitalbazaar/x25519-key-agreement-key-2019'; - -import * as didIo from '@digitalbazaar/did-io'; +import { + X25519KeyAgreementKey2020 +} from '@digitalbazaar/x25519-key-agreement-key-2020'; const DID_CONTEXT_URL = 'https://www.w3.org/ns/did/v1'; // For backwards compat only, not actually importing this suite @@ -133,6 +133,7 @@ export class DidKeyDriver { if(!did) { throw new TypeError('"did" must be a string.'); } + validateDidKey({did}); const [didAuthority, keyIdFragment] = did.split('#'); diff --git a/test/driver.spec.js b/test/driver.spec.js index 9078324..4add99c 100644 --- a/test/driver.spec.js +++ b/test/driver.spec.js @@ -2,15 +2,14 @@ * Copyright (c) 2019-20201 Digital Bazaar, Inc. All rights reserved. */ import chai from 'chai'; -chai.should(); -const {expect} = chai; - -import {Ed25519VerificationKey2020} from - '@digitalbazaar/ed25519-verification-key-2020'; +import {driver} from '../lib/index.js'; import {Ed25519VerificationKey2018} from '@digitalbazaar/ed25519-verification-key-2018'; -import {driver} from '../lib/index.js'; +import {Ed25519VerificationKey2020} from + '@digitalbazaar/ed25519-verification-key-2020'; +chai.should(); +const {expect} = chai; const didKeyDriver = driver(); // eslint-disable-next-line max-len From 806718273046b524e1a27b06fcf30d72bd1fca56 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 4 Aug 2022 20:23:03 -0400 Subject: [PATCH 05/44] Port validators over. --- lib/{DidKeyError.js => DidError.js} | 6 +-- lib/validator.js | 65 ++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 4 deletions(-) rename lib/{DidKeyError.js => DidError.js} (72%) diff --git a/lib/DidKeyError.js b/lib/DidError.js similarity index 72% rename from lib/DidKeyError.js rename to lib/DidError.js index 1100d42..becfb0a 100644 --- a/lib/DidKeyError.js +++ b/lib/DidError.js @@ -9,10 +9,10 @@ * @param {string} code - An error code from the did:key spec. * */ -export class DidKeyError extends Error { - constructor(message, code) { +export class DidError extends SyntaxError { + constructor({message, code}) { super(message); - this.name = 'DidKeyError'; + this.name = 'DidError'; this.code = code; } } diff --git a/lib/validator.js b/lib/validator.js index a723210..55fefae 100644 --- a/lib/validator.js +++ b/lib/validator.js @@ -1,7 +1,70 @@ /*! * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. */ -import {DidKeyError} from './DidKeyError.js'; +import {DidError} from './DidError.js'; export const validateDidKey = ({did}) => { }; + +/** + * General validation for did:keys independent + * of key type specific validation. + * + * @param {object} options - Options to use. + * @param {string} options.method - A did:method. + * @param {string|number} options.version - A did:method:version. + * @param {string} [options.multibase = ''] - The multibase value + * of the did:key. + * + * @throws {Error} Throws general did:key errors. + * + * @returns {undefined} If the didKeyComponents are valid. + */ +function _validateDidKey({ + method, + version, + multibase = '', +}) { + if(method !== 'key') { + throw new DidError({ + message: `Method must be "key" received "${method}"`, + code: 'invalidDid' + }); + } + if(!multibase.startsWith('z')) { + throw new DidError({ + message: `Multibase must start with "z" received ${multibase[0]}`, + code: 'invalidDid' + }); + } + _validateVersion({version}); +} + +/** + * A version must be convertible to a positive integer. + * + * @param {object} options - Options to use. + * @param {string|number} options.version - A did:key:version. + * + * @throws {Error} Throws InvalidDid. + * + * @returns {undefined} Returns on success. + */ +function _validateVersion({version}) { + try { + const versionNumber = Number.parseInt(version); + if(versionNumber <= 0) { + throw new DidError({ + message: 'Version must be a positive integer received ' + + `"${versionNumber}"`, + code: 'invalidDid' + }); + } + } catch(e) { + throw new DidError({ + message: `Version must be an integer received "${version}"`, + code: 'invalidDid' + }); + } +} + From d8e7b3cc47d7167d1f652517f7327783fcc90d1a Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 5 Aug 2022 11:41:57 -0400 Subject: [PATCH 06/44] Use ed25519 verification key with did key error causes. --- lib/{validator.js => validators.js} | 0 package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/{validator.js => validators.js} (100%) diff --git a/lib/validator.js b/lib/validators.js similarity index 100% rename from lib/validator.js rename to lib/validators.js diff --git a/package.json b/package.json index af831cf..965618b 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ ], "dependencies": { "@digitalbazaar/did-io": "^2.0.0", - "@digitalbazaar/ed25519-verification-key-2020": "^4.0.0", + "@digitalbazaar/ed25519-verification-key-2020": "digitalbazaar/ed25519-verification-key-2020#add-did-key-error-codes", "@digitalbazaar/x25519-key-agreement-key-2019": "^6.0.0", "@digitalbazaar/x25519-key-agreement-key-2020": "^3.0.0" }, From 018cbd736b22e85ce798bc97a3375e43dba84b50 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 5 Aug 2022 14:39:20 -0400 Subject: [PATCH 07/44] Use DidResolutionError from did-io. --- lib/DidError.js | 18 ------------------ lib/validators.js | 10 +++++----- package.json | 2 +- 3 files changed, 6 insertions(+), 24 deletions(-) delete mode 100644 lib/DidError.js diff --git a/lib/DidError.js b/lib/DidError.js deleted file mode 100644 index becfb0a..0000000 --- a/lib/DidError.js +++ /dev/null @@ -1,18 +0,0 @@ -/*! - * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. - */ - -/** - * Used to throw DidKeyErrors. - * - * @param {string} message - An error message. - * @param {string} code - An error code from the did:key spec. - * - */ -export class DidError extends SyntaxError { - constructor({message, code}) { - super(message); - this.name = 'DidError'; - this.code = code; - } -} diff --git a/lib/validators.js b/lib/validators.js index 55fefae..0198440 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -1,7 +1,7 @@ /*! * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. */ -import {DidError} from './DidError.js'; +import {DidResolutionError} from 'did-io'; export const validateDidKey = ({did}) => { }; @@ -26,13 +26,13 @@ function _validateDidKey({ multibase = '', }) { if(method !== 'key') { - throw new DidError({ + throw new DidResolutionError({ message: `Method must be "key" received "${method}"`, code: 'invalidDid' }); } if(!multibase.startsWith('z')) { - throw new DidError({ + throw new DidResolutionError({ message: `Multibase must start with "z" received ${multibase[0]}`, code: 'invalidDid' }); @@ -54,14 +54,14 @@ function _validateVersion({version}) { try { const versionNumber = Number.parseInt(version); if(versionNumber <= 0) { - throw new DidError({ + throw new DidResolutionError({ message: 'Version must be a positive integer received ' + `"${versionNumber}"`, code: 'invalidDid' }); } } catch(e) { - throw new DidError({ + throw new DidResolutionError({ message: `Version must be an integer received "${version}"`, code: 'invalidDid' }); diff --git a/package.json b/package.json index 965618b..8fd5cb4 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "lib/**/*.js" ], "dependencies": { - "@digitalbazaar/did-io": "^2.0.0", + "@digitalbazaar/did-io": "digitalbazaar/did-io#add-did-key-spec-7-validators", "@digitalbazaar/ed25519-verification-key-2020": "digitalbazaar/ed25519-verification-key-2020#add-did-key-error-codes", "@digitalbazaar/x25519-key-agreement-key-2019": "^6.0.0", "@digitalbazaar/x25519-key-agreement-key-2020": "^3.0.0" From 64d7741ac4867f715afc76560e7820468fe42764 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 5 Aug 2022 15:16:08 -0400 Subject: [PATCH 08/44] Add parseDidKey to validators & use it. --- lib/validators.js | 52 +++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/lib/validators.js b/lib/validators.js index 0198440..aab577c 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -1,38 +1,29 @@ /*! * Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved. */ -import {DidResolutionError} from 'did-io'; -export const validateDidKey = ({did}) => { - -}; +import {DidResolverError} from '@digitalbazaar/did-io'; /** * General validation for did:keys independent * of key type specific validation. * * @param {object} options - Options to use. - * @param {string} options.method - A did:method. - * @param {string|number} options.version - A did:method:version. - * @param {string} [options.multibase = ''] - The multibase value - * of the did:key. + * @param {string} options.did - A did:key. * - * @throws {Error} Throws general did:key errors. + * @throws {DidResolverError} Throws general did:key errors. * * @returns {undefined} If the didKeyComponents are valid. */ -function _validateDidKey({ - method, - version, - multibase = '', -}) { +export function validateDidKey({did}) { + const {method, version, multibase} = parseDidKey({did}); if(method !== 'key') { - throw new DidResolutionError({ + throw new DidResolverError({ message: `Method must be "key" received "${method}"`, code: 'invalidDid' }); } if(!multibase.startsWith('z')) { - throw new DidResolutionError({ + throw new DidResolverError({ message: `Multibase must start with "z" received ${multibase[0]}`, code: 'invalidDid' }); @@ -54,17 +45,42 @@ function _validateVersion({version}) { try { const versionNumber = Number.parseInt(version); if(versionNumber <= 0) { - throw new DidResolutionError({ + throw new DidResolverError({ message: 'Version must be a positive integer received ' + `"${versionNumber}"`, code: 'invalidDid' }); } } catch(e) { - throw new DidResolutionError({ + throw new DidResolverError({ message: `Version must be an integer received "${version}"`, code: 'invalidDid' }); } } +export function parseDidKey({did}) { + const { + scheme, + method, + version = '', + multibase + } = _getBaseDidKey({did}); + return { + scheme, + method, + version: version.length === 0 ? '1' : version, + multibase + }; +} + +// if we have a did url we need to remove those components +// so we can correctly parse the identifier after the method +function _getBaseDidKey({did}) { + const pchar = '[a-zA-Z0-9\\-\\._~]|%[0-9a-fA-F]{2}|[!$&\'()*+,;=:@]'; + const didKeyPattern = '(?did):(?key):?(?\\d*)' + + `:(?z(${pchar})+)`; + // this can return null if scheme, method, and multibase are not defined + const {groups = {}} = (new RegExp(didKeyPattern).exec(did) || {}); + return groups; +} From 3621bf99798818c1e8f45ff6b036d1526bd01715 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 8 Aug 2022 16:22:52 -0400 Subject: [PATCH 09/44] Use main for ed25519-verification-key-2020. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8fd5cb4..b853220 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ ], "dependencies": { "@digitalbazaar/did-io": "digitalbazaar/did-io#add-did-key-spec-7-validators", - "@digitalbazaar/ed25519-verification-key-2020": "digitalbazaar/ed25519-verification-key-2020#add-did-key-error-codes", + "@digitalbazaar/ed25519-verification-key-2020": "digitalbazaar/ed25519-verification-key-2020", "@digitalbazaar/x25519-key-agreement-key-2019": "^6.0.0", "@digitalbazaar/x25519-key-agreement-key-2020": "^3.0.0" }, From 971ee7cc23b050edee779e43a155d50575581eb2 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 9 Aug 2022 10:25:52 -0400 Subject: [PATCH 10/44] Assert on did key scheme. --- lib/validators.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/validators.js b/lib/validators.js index aab577c..0d622f4 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -15,7 +15,13 @@ import {DidResolverError} from '@digitalbazaar/did-io'; * @returns {undefined} If the didKeyComponents are valid. */ export function validateDidKey({did}) { - const {method, version, multibase} = parseDidKey({did}); + const {scheme, method, version, multibase} = parseDidKey({did}); + if(scheme !== 'did') { + throw new DidResolverError({ + message: `Scheme must be "did" received "${scheme}"`, + code: 'invalidDid' + }); + } if(method !== 'key') { throw new DidResolverError({ message: `Method must be "key" received "${method}"`, From 9f6089fe7c08e2554d87033ddc390979612ab23e Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 9 Aug 2022 10:26:14 -0400 Subject: [PATCH 11/44] Start Major release changelog for did:key v4. --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e96259d..f440cdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # did:key driver ChangeLog +## 4.0.0 - + +### Added +- **BREAKING**: Implemented validators in did resolver. +- **BREAKING**: Added added validators for didDocument creation. +- Added options for didDocument creation. + ## 3.0.0 - 2022-06-02 ### Changed From 46b20583f7f93016d296babb4d51ca1761ce41eb Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 9 Aug 2022 15:40:32 -0400 Subject: [PATCH 12/44] Start work on validating did document creation options. --- lib/DidKeyDriver.js | 103 +++++++++++++++++++++++++++++++------------- lib/validators.js | 81 ++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 30 deletions(-) diff --git a/lib/DidKeyDriver.js b/lib/DidKeyDriver.js index b941810..9717d6a 100644 --- a/lib/DidKeyDriver.js +++ b/lib/DidKeyDriver.js @@ -27,14 +27,26 @@ const contextsBySuite = new Map([ export class DidKeyDriver { /** - * @param {object} options - Options hashmap. + * @param {object} options - Options to use. * @param {object} [options.verificationSuite=Ed25519VerificationKey2020] - * Key suite for the signature verification key suite to use. + * @param {Array} options.publicKeyFormats - Which formats of keys + * are supported. */ - constructor({verificationSuite = Ed25519VerificationKey2020} = {}) { + constructor({ + verificationSuite = Ed25519VerificationKey2020, + publicKeyFormats = [ + 'Ed25519VerificationKey2018', + 'Ed25519VerificationKey2020', + 'X25519KeyAgreementKey2019', + 'X25519KeyAgreementKey2020', + 'JsonWebKey2020' + ] + } = {}) { // used by did-io to register drivers this.method = 'key'; this.verificationSuite = verificationSuite; + this.publicKeyFormats = publicKeyFormats; } /** @@ -44,13 +56,14 @@ export class DidKeyDriver { * @param {object} options - Options hashmap. * @param {Uint8Array} [options.seed] - A 32-byte array seed for a * deterministic key. + * @param {object} [options.options] - Did Document creation options. * * @returns {Promise<{didDocument: object, keyPairs: Map, * methodFor: Function}>} Resolves with the generated DID Document, along * with the corresponding key pairs used to generate it (for storage in a * KMS). */ - async generate({seed} = {}) { + async generate({seed, options} = {}) { // Public/private key pair of the main did:key signing/verification key const verificationKeyPair = await this.verificationSuite.generate({seed}); @@ -58,7 +71,8 @@ export class DidKeyDriver { // the verificationKeyPair above, but also the keyAgreementKey pair that // is derived from the verification key pair. const {didDocument, keyPairs} = await this._keyPairToDidDocument({ - keyPair: verificationKeyPair + keyPair: verificationKeyPair, + options }); // Convenience function that returns the public/private key pair instance @@ -124,11 +138,12 @@ export class DidKeyDriver { * or an x25519 key-agreement key id). * @param {string} [options.url] - Alias for the `did` url param, supported * for better readability of invoking code. + * @param {object} [options.options] - Options for didDocument creation. * * @returns {Promise} Resolves to a DID Document or a * public key node with context. */ - async get({did, url} = {}) { + async get({did, url, options} = {}) { did = did || url; if(!did) { throw new TypeError('"did" must be a string.'); @@ -140,7 +155,7 @@ export class DidKeyDriver { const fingerprint = didAuthority.substr('did:key:'.length); const keyPair = this.verificationSuite.fromFingerprint({fingerprint}); - const {didDocument} = await this._keyPairToDidDocument({keyPair}); + const {didDocument} = await this._keyPairToDidDocument({keyPair, options}); if(keyIdFragment) { // resolve an individual key @@ -162,12 +177,14 @@ export class DidKeyDriver { * used to generate the DID document (either an LDKeyPair instance * containing public key material, or a "key description" plain object * (such as that generated from a KMS)). + * @param {object} [options.options] - Options for didDocument creation. * * @returns {Promise} Resolves with the generated DID Document. */ - async publicKeyToDidDoc({publicKeyDescription} = {}) { + async publicKeyToDidDoc({publicKeyDescription, options} = {}) { const {didDocument} = await this._keyPairToDidDocument({ - keyPair: publicKeyDescription + keyPair: publicKeyDescription, + options }); return {didDocument}; } @@ -179,44 +196,39 @@ export class DidKeyDriver { * @param {LDKeyPair|object} options.keyPair - Key used to generate the DID * document (either an LDKeyPair instance containing public key material, * or a "key description" plain object (such as that generated from a KMS)). + * @param {object} [options.options = defaultOptions] - Options for + * didDocument creation. * * @returns {Promise<{didDocument: object, keyPairs: Map}>} * Resolves with the generated DID Document, along with the corresponding * key pairs used to generate it (for storage in a KMS). */ - async _keyPairToDidDocument({keyPair} = {}) { + async _keyPairToDidDocument({keyPair, options = {}} = {}) { + const { + // NOTE: encryptionKeyFormat will need to be X25519KeyAgreementKey2020 + publicKeyFormat = 'Ed25519VerificationKey2020', + enableExperimentalPublicKeyTypes = false, + defaultContext = [DID_CONTEXT_URL], + enableEncryptionKeyDerivation = true + } = options; const verificationKeyPair = await this.verificationSuite.from({...keyPair}); const did = `did:key:${verificationKeyPair.fingerprint()}`; verificationKeyPair.controller = did; - const contexts = [DID_CONTEXT_URL]; + const contexts = [...defaultContext]; // The KAK pair will use the source key's controller, but will generate // its own .id - let keyAgreementKeyPair; - if(verificationKeyPair.type === 'Ed25519VerificationKey2020') { - keyAgreementKeyPair = X25519KeyAgreementKey2020 - .fromEd25519VerificationKey2020({keyPair: verificationKeyPair}); - contexts.push(Ed25519VerificationKey2020.SUITE_CONTEXT, - X25519KeyAgreementKey2020.SUITE_CONTEXT); - } else if(verificationKeyPair.type === 'Ed25519VerificationKey2018') { - keyAgreementKeyPair = X25519KeyAgreementKey2019 - .fromEd25519VerificationKey2018({keyPair: verificationKeyPair}); - contexts.push(ED25519_KEY_2018_CONTEXT_URL, - X25519KeyAgreementKey2019.SUITE_CONTEXT); - } else { - throw new Error( - 'Cannot derive key agreement key from verification key type "' + - verificationKeyPair.type + '".' - ); - } + const keyAgreementKeyPair = this._getEncryptionMethod({ + verificationKeyPair, + contexts + }); // Now set the source key's id verificationKeyPair.id = `${did}#${verificationKeyPair.fingerprint()}`; // get the public components of each keypair const publicEdKey = verificationKeyPair.export({publicKey: true}); - const publicDhKey = keyAgreementKeyPair.export({publicKey: true}); // Compose the DID Document const didDocument = { @@ -228,9 +240,12 @@ export class DidKeyDriver { authentication: [publicEdKey.id], assertionMethod: [publicEdKey.id], capabilityDelegation: [publicEdKey.id], - capabilityInvocation: [publicEdKey.id], - keyAgreement: [publicDhKey] + capabilityInvocation: [publicEdKey.id] }; + if(enableEncryptionKeyDerivation) { + const publicDhKey = keyAgreementKeyPair.export({publicKey: true}); + didDocument.keyAgreement = [publicDhKey]; + } // create the key pairs map const keyPairs = new Map(); @@ -239,6 +254,34 @@ export class DidKeyDriver { return {didDocument, keyPairs}; } + /** + * Gets the encryption method (which should be the keyAgreementKey). + * + * @param {object} options - Options to use. + * @param {object} options.verificationKeyPair - The verification key Pair. + * @param {Array} options.contexts - The contexts for the did + * document and keys. + * + * @returns {object} The encryption key. + */ + _getEncryptionMethod({verificationKeyPair, contexts}) { + if(verificationKeyPair.type === 'Ed25519VerificationKey2020') { + contexts.push(Ed25519VerificationKey2020.SUITE_CONTEXT, + X25519KeyAgreementKey2020.SUITE_CONTEXT); + return X25519KeyAgreementKey2020 + .fromEd25519VerificationKey2020({keyPair: verificationKeyPair}); + } else if(verificationKeyPair.type === 'Ed25519VerificationKey2018') { + contexts.push(ED25519_KEY_2018_CONTEXT_URL, + X25519KeyAgreementKey2019.SUITE_CONTEXT); + return X25519KeyAgreementKey2019 + .fromEd25519VerificationKey2018({keyPair: verificationKeyPair}); + } else { + throw new Error( + 'Cannot derive key agreement key from verification key type "' + + verificationKeyPair.type + '".' + ); + } + } /** * Computes and returns the id of a given key pair. Used by `did-io` drivers. diff --git a/lib/validators.js b/lib/validators.js index 0d622f4..e94a39d 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -37,6 +37,79 @@ export function validateDidKey({did}) { _validateVersion({version}); } +// FIXME while we do validate public key formats we do not +// convert to the requested public key format. +/** + * Public did:keys can be represented in multiple formats. + * While we don't do any conversion in this library we still make + * the check. + * + * @param {object} options - Options to use. + * @param {Array} options.publicKeyFormats - A list of + * public key formats our did:key implementation supports. + * @param {object} options.didOptions - The didOptions from searchParams + * and headers. + * @param {string} options.didOptions.publicKeyFormat - The format + * the public key should be returned in. + * @param {string} options.didOptions.enableExperimentalPublicKeyTypes - An + * option that can be passed in to allow experimental key types. + * @param {object} options.didDocument - The didDocument requred by the did + * or didUrl. + * + * @throws {Error} Throws UnsupportedPublicKeyType or InvalidPublicKeyType. + * + * @returns {undefined} Returns on sucess. + */ +export function validateDidDocument({ + publicKeyFormats = [], + didOptions: { + publicKeyFormat, + enableExperimentalPublicKeyTypes + }, + didDocument, +}) { + // if no publicKeyFormat was in the options just skip this check + if(!publicKeyFormat) { + return; + } + // supported public key formats are set manually on config + if(!publicKeyFormats.includes(publicKeyFormat)) { + throw new DidResolverError({ + message: `Unsupported public key type ${publicKeyFormat}`, + code: 'unsupportedPublicKeyType' + }); + } + // all of the other did methods so far are signature verification + if(!enableExperimentalPublicKeyTypes) { + const verificationFormats = ['Multikey', 'JsonWebKey2020']; + //keyAgreement is an encryption verification method + if(didDocument.type === 'X25519KeyAgreementKey2020') { + const encryptionFormats = [ + ...verificationFormats, + 'X25519KeyAgreementKey2020' + ]; + if(!encryptionFormats.includes(publicKeyFormat)) { + throw new DidResolverError({ + message: `Invalid Public Key Type ${publicKeyFormat}`, + code: 'invalidPublicKeyType' + }); + } + // no further checks needed + return; + } + const signatureFormats = [ + ...verificationFormats, + 'Ed25519VerificationKey2020' + ]; + if(!signatureFormats.includes(publicKeyFormat)) { + throw new DidResolverError({ + message: `Invalid Public Key Type ${publicKeyFormat}`, + code: 'invalidPublicKeyType' + }); + } + } +} + /** * A version must be convertible to a positive integer. * @@ -90,3 +163,11 @@ function _getBaseDidKey({did}) { const {groups = {}} = (new RegExp(didKeyPattern).exec(did) || {}); return groups; } + +function _validateEncryptionMethod({}) { + +} + +function _validateSignatureMethod({}) { + +} From 6f4233a8f743fe11ab3ea29aaf8d99a0b25f9a35 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 9 Aug 2022 18:21:34 -0400 Subject: [PATCH 13/44] Add didDocument validator. --- lib/DidKeyDriver.js | 13 +++++++-- lib/validators.js | 67 ++++++++++++++++++++++++++------------------- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/lib/DidKeyDriver.js b/lib/DidKeyDriver.js index 9717d6a..67c9188 100644 --- a/lib/DidKeyDriver.js +++ b/lib/DidKeyDriver.js @@ -2,10 +2,10 @@ * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ import * as didIo from '@digitalbazaar/did-io'; +import {validateDidDocument, validateDidKey} from './validators.js'; import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020'; -import {validateDidKey} from './validators.js'; import { X25519KeyAgreementKey2019 } from '@digitalbazaar/x25519-key-agreement-key-2019'; @@ -205,7 +205,6 @@ export class DidKeyDriver { */ async _keyPairToDidDocument({keyPair, options = {}} = {}) { const { - // NOTE: encryptionKeyFormat will need to be X25519KeyAgreementKey2020 publicKeyFormat = 'Ed25519VerificationKey2020', enableExperimentalPublicKeyTypes = false, defaultContext = [DID_CONTEXT_URL], @@ -246,7 +245,15 @@ export class DidKeyDriver { const publicDhKey = keyAgreementKeyPair.export({publicKey: true}); didDocument.keyAgreement = [publicDhKey]; } - + validateDidDocument({ + didDocument, + didOptions: { + publicKeyFormat, + enableExperimentalPublicKeyTypes, + enableEncryptionKeyDerivation + }, + publicKeyFormats: this.publicKeyFormats + }); // create the key pairs map const keyPairs = new Map(); keyPairs.set(verificationKeyPair.id, verificationKeyPair); diff --git a/lib/validators.js b/lib/validators.js index e94a39d..a715914 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -53,6 +53,8 @@ export function validateDidKey({did}) { * the public key should be returned in. * @param {string} options.didOptions.enableExperimentalPublicKeyTypes - An * option that can be passed in to allow experimental key types. + * @param {boolean} options.didOptions.enableEncryptionKeyDerivation - If + * to add the encryption key to the didDocument. * @param {object} options.didDocument - The didDocument requred by the did * or didUrl. * @@ -64,7 +66,8 @@ export function validateDidDocument({ publicKeyFormats = [], didOptions: { publicKeyFormat, - enableExperimentalPublicKeyTypes + enableExperimentalPublicKeyTypes, + enableEncryptionKeyDerivation }, didDocument, }) { @@ -82,31 +85,16 @@ export function validateDidDocument({ // all of the other did methods so far are signature verification if(!enableExperimentalPublicKeyTypes) { const verificationFormats = ['Multikey', 'JsonWebKey2020']; - //keyAgreement is an encryption verification method - if(didDocument.type === 'X25519KeyAgreementKey2020') { - const encryptionFormats = [ - ...verificationFormats, - 'X25519KeyAgreementKey2020' - ]; - if(!encryptionFormats.includes(publicKeyFormat)) { - throw new DidResolverError({ - message: `Invalid Public Key Type ${publicKeyFormat}`, - code: 'invalidPublicKeyType' - }); - } - // no further checks needed - return; - } - const signatureFormats = [ - ...verificationFormats, - 'Ed25519VerificationKey2020' - ]; - if(!signatureFormats.includes(publicKeyFormat)) { - throw new DidResolverError({ - message: `Invalid Public Key Type ${publicKeyFormat}`, - code: 'invalidPublicKeyType' + if(enableEncryptionKeyDerivation) { + _validateEncryptionMethod({ + method: didDocument.keyAgreement, + verificationFormats }); } + _validateSignatureMethod({ + method: didDocument.verificationMethod, + verificationFormats + }); } } @@ -164,10 +152,33 @@ function _getBaseDidKey({did}) { return groups; } -function _validateEncryptionMethod({}) { - +function _validateEncryptionMethod({method, verificationFormats}) { + //keyAgreement is an encryption verification method + const encryptionFormats = [ + ...verificationFormats, + 'X25519KeyAgreementKey2020' + ]; + for(const {type} of method) { + if(!encryptionFormats.includes(type)) { + throw new DidResolverError({ + message: `Invalid Public Key Type ${type}`, + code: 'invalidPublicKeyType' + }); + } + } } -function _validateSignatureMethod({}) { - +function _validateSignatureMethod({method, verificationFormats}) { + const signatureFormats = [ + ...verificationFormats, + 'Ed25519VerificationKey2020' + ]; + for(const {type} of method) { + if(!signatureFormats.includes(type)) { + throw new DidResolverError({ + message: `Invalid Public Key Type ${method.type}`, + code: 'invalidPublicKeyType' + }); + } + } } From cfd3765496dbe6040eea662242ae1f510d1e4e22 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 10 Aug 2022 09:55:21 -0400 Subject: [PATCH 14/44] Use enableExperimentalPublicKeyTypes: true for 2018 ed key. --- test/driver.spec.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/driver.spec.js b/test/driver.spec.js index 4add99c..4db3bd1 100644 --- a/test/driver.spec.js +++ b/test/driver.spec.js @@ -61,7 +61,8 @@ describe('did:key method driver', () => { }); // Note: Testing same keys as previous (2020 mode) test const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; - const didDocument = await didKeyDriver2018.get({did}); + const options = {enableExperimentalPublicKeyTypes: true}; + const didDocument = await didKeyDriver2018.get({did, options}); const expectedDidDoc = { '@context': [ @@ -132,7 +133,8 @@ describe('did:key method driver', () => { }); const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; const keyId = did + '#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; - const key = await didKeyDriver2018.get({did: keyId}); + const options = {enableExperimentalPublicKeyTypes: true}; + const key = await didKeyDriver2018.get({did: keyId, options}); expect(key).to.eql({ '@context': 'https://w3id.org/security/suites/ed25519-2018/v1', @@ -167,7 +169,8 @@ describe('did:key method driver', () => { const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; const kakKeyId = `${did}#z6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc`; - const key = await didKeyDriver2018.get({did: kakKeyId}); + const options = {enableExperimentalPublicKeyTypes: true}; + const key = await didKeyDriver2018.get({did: kakKeyId, options}); expect(key).to.eql({ '@context': 'https://w3id.org/security/suites/x25519-2019/v1', From 0f58597e9d94e378de84d3a57fb0069e30c5f0b9 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 10 Aug 2022 10:13:36 -0400 Subject: [PATCH 15/44] Use @digitalbazaar/ed25519-verification-key-2020: ^4.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b853220..01797b4 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ ], "dependencies": { "@digitalbazaar/did-io": "digitalbazaar/did-io#add-did-key-spec-7-validators", - "@digitalbazaar/ed25519-verification-key-2020": "digitalbazaar/ed25519-verification-key-2020", + "@digitalbazaar/ed25519-verification-key-2020": "^4.1.0", "@digitalbazaar/x25519-key-agreement-key-2019": "^6.0.0", "@digitalbazaar/x25519-key-agreement-key-2020": "^3.0.0" }, From 8d69300303580e5c7200ba3e29c535ae3253ee7d Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 10 Aug 2022 12:52:06 -0400 Subject: [PATCH 16/44] Use publicKeyFormat to get verificationMethod. --- lib/DidKeyDriver.js | 69 +++++++++++++++++++++++++++++---------------- lib/validators.js | 13 --------- test/driver.spec.js | 38 ++++++++++++++----------- 3 files changed, 65 insertions(+), 55 deletions(-) diff --git a/lib/DidKeyDriver.js b/lib/DidKeyDriver.js index 67c9188..ec49652 100644 --- a/lib/DidKeyDriver.js +++ b/lib/DidKeyDriver.js @@ -3,6 +3,9 @@ */ import * as didIo from '@digitalbazaar/did-io'; import {validateDidDocument, validateDidKey} from './validators.js'; +import {DidResolverError} from '@digitalbazaar/did-io'; +import {Ed25519VerificationKey2018} from + '@digitalbazaar/ed25519-verification-key-2018'; import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020'; @@ -25,28 +28,34 @@ const contextsBySuite = new Map([ [X25519KeyAgreementKey2019.suite, X25519KeyAgreementKey2019.SUITE_CONTEXT] ]); +const VMsByKeyFormat = new Map([ + ['Ed25519VerificationKey2018', Ed25519VerificationKey2018], + ['Ed25519VerificationKey2020', Ed25519VerificationKey2020], + ['X25519KeyAgreementKey2019', X25519KeyAgreementKey2019], + ['X25519KeyAgreementKey2020', X25519KeyAgreementKey2020], + // this is an exception where we convert an ed key to json web key + ['JsonWebKey2020', Ed25519VerificationKey2020] +]); + +const defaultOptions = { + publicKeyFormat: 'Ed25519VerificationKey2020', + enableExperimentalPublicKeyTypes: false, + defaultContext: [DID_CONTEXT_URL], + enableEncryptionKeyDerivation: true +}; + export class DidKeyDriver { /** * @param {object} options - Options to use. - * @param {object} [options.verificationSuite=Ed25519VerificationKey2020] - - * Key suite for the signature verification key suite to use. - * @param {Array} options.publicKeyFormats - Which formats of keys - * are supported. + * @param {Map} [options.verificationMethods=VMsByKeyFormat] - + * A map of verification methods with the key as the publicKeyFormat. */ constructor({ - verificationSuite = Ed25519VerificationKey2020, - publicKeyFormats = [ - 'Ed25519VerificationKey2018', - 'Ed25519VerificationKey2020', - 'X25519KeyAgreementKey2019', - 'X25519KeyAgreementKey2020', - 'JsonWebKey2020' - ] + verificationMethods = VMsByKeyFormat, } = {}) { // used by did-io to register drivers this.method = 'key'; - this.verificationSuite = verificationSuite; - this.publicKeyFormats = publicKeyFormats; + this.verificationMethods = verificationMethods; } /** @@ -63,10 +72,10 @@ export class DidKeyDriver { * with the corresponding key pairs used to generate it (for storage in a * KMS). */ - async generate({seed, options} = {}) { + async generate({seed, options = {}} = {}) { // Public/private key pair of the main did:key signing/verification key - const verificationKeyPair = await this.verificationSuite.generate({seed}); - + const verificationKeyPair = await this._getVerificationMethod(options). + generate({seed}); // keyPairs is a map of keyId to key pair instance, that includes // the verificationKeyPair above, but also the keyAgreementKey pair that // is derived from the verification key pair. @@ -138,12 +147,12 @@ export class DidKeyDriver { * or an x25519 key-agreement key id). * @param {string} [options.url] - Alias for the `did` url param, supported * for better readability of invoking code. - * @param {object} [options.options] - Options for didDocument creation. + * @param {object} [options.options = {}] - Options for didDocument creation. * * @returns {Promise} Resolves to a DID Document or a * public key node with context. */ - async get({did, url, options} = {}) { + async get({did, url, options = {}} = {}) { did = did || url; if(!did) { throw new TypeError('"did" must be a string.'); @@ -153,7 +162,8 @@ export class DidKeyDriver { const [didAuthority, keyIdFragment] = did.split('#'); const fingerprint = didAuthority.substr('did:key:'.length); - const keyPair = this.verificationSuite.fromFingerprint({fingerprint}); + const keyPair = this._getVerificationMethod(options). + fromFingerprint({fingerprint}); const {didDocument} = await this._keyPairToDidDocument({keyPair, options}); @@ -205,12 +215,13 @@ export class DidKeyDriver { */ async _keyPairToDidDocument({keyPair, options = {}} = {}) { const { - publicKeyFormat = 'Ed25519VerificationKey2020', + publicKeyFormat, enableExperimentalPublicKeyTypes = false, defaultContext = [DID_CONTEXT_URL], enableEncryptionKeyDerivation = true } = options; - const verificationKeyPair = await this.verificationSuite.from({...keyPair}); + const verificationKeyPair = await this._getVerificationMethod( + {publicKeyFormat}).from({...keyPair}); const did = `did:key:${verificationKeyPair.fingerprint()}`; verificationKeyPair.controller = did; @@ -248,11 +259,9 @@ export class DidKeyDriver { validateDidDocument({ didDocument, didOptions: { - publicKeyFormat, enableExperimentalPublicKeyTypes, enableEncryptionKeyDerivation - }, - publicKeyFormats: this.publicKeyFormats + } }); // create the key pairs map const keyPairs = new Map(); @@ -289,6 +298,16 @@ export class DidKeyDriver { ); } } + _getVerificationMethod({publicKeyFormat = 'Ed25519VerificationKey2020'}) { + const verificationMethod = this.verificationMethods.get(publicKeyFormat); + if(!verificationMethod) { + throw new DidResolverError({ + message: `Unsupported public key type ${publicKeyFormat}`, + code: 'unsupportedPublicKeyType' + }); + } + return verificationMethod; + } /** * Computes and returns the id of a given key pair. Used by `did-io` drivers. diff --git a/lib/validators.js b/lib/validators.js index a715914..b24457f 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -63,25 +63,12 @@ export function validateDidKey({did}) { * @returns {undefined} Returns on sucess. */ export function validateDidDocument({ - publicKeyFormats = [], didOptions: { - publicKeyFormat, enableExperimentalPublicKeyTypes, enableEncryptionKeyDerivation }, didDocument, }) { - // if no publicKeyFormat was in the options just skip this check - if(!publicKeyFormat) { - return; - } - // supported public key formats are set manually on config - if(!publicKeyFormats.includes(publicKeyFormat)) { - throw new DidResolverError({ - message: `Unsupported public key type ${publicKeyFormat}`, - code: 'unsupportedPublicKeyType' - }); - } // all of the other did methods so far are signature verification if(!enableExperimentalPublicKeyTypes) { const verificationFormats = ['Multikey', 'JsonWebKey2020']; diff --git a/test/driver.spec.js b/test/driver.spec.js index 4db3bd1..8e477a5 100644 --- a/test/driver.spec.js +++ b/test/driver.spec.js @@ -3,8 +3,6 @@ */ import chai from 'chai'; import {driver} from '../lib/index.js'; -import {Ed25519VerificationKey2018} from - '@digitalbazaar/ed25519-verification-key-2018'; import {Ed25519VerificationKey2020} from '@digitalbazaar/ed25519-verification-key-2020'; @@ -55,13 +53,15 @@ describe('did:key method driver', () => { .equal('z6LSotGbgPCJD2Y6TSvvgxERLTfVZxCh9KSrez3WNrNp7vKW'); }); - it('should get the DID Doc in 2018 mode', async () => { - const didKeyDriver2018 = driver({ - verificationSuite: Ed25519VerificationKey2018 - }); + it('should get the DID Doc in with publicKeyFormat ' + + 'Ed25519VerificationKey2018', async () => { + const didKeyDriver2018 = driver(); // Note: Testing same keys as previous (2020 mode) test const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; - const options = {enableExperimentalPublicKeyTypes: true}; + const options = { + publicKeyFormat: 'Ed25519VerificationKey2018', + enableExperimentalPublicKeyTypes: true + }; const didDocument = await didKeyDriver2018.get({did, options}); const expectedDidDoc = { @@ -127,13 +127,15 @@ describe('did:key method driver', () => { }); }); - it('should resolve an individual key in 2018 mode', async () => { - const didKeyDriver2018 = driver({ - verificationSuite: Ed25519VerificationKey2018 - }); + it('should resolve an individual key with publicKeyFormat ' + + 'Ed25519VerificationKey2018', async () => { + const didKeyDriver2018 = driver(); const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; const keyId = did + '#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; - const options = {enableExperimentalPublicKeyTypes: true}; + const options = { + publicKeyFormat: 'Ed25519VerificationKey2018', + enableExperimentalPublicKeyTypes: true + }; const key = await didKeyDriver2018.get({did: keyId, options}); expect(key).to.eql({ @@ -162,14 +164,16 @@ describe('did:key method driver', () => { }); }); - it('should resolve an individual key agreement key (2018)', async () => { - const didKeyDriver2018 = driver({ - verificationSuite: Ed25519VerificationKey2018 - }); + it('should resolve an individual key agreement key using publicKeyFormat ' + + 'Ed25519VerificationKey2018', async () => { + const didKeyDriver2018 = driver(); const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; const kakKeyId = `${did}#z6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc`; - const options = {enableExperimentalPublicKeyTypes: true}; + const options = { + publicKeyFormat: 'Ed25519VerificationKey2018', + enableExperimentalPublicKeyTypes: true + }; const key = await didKeyDriver2018.get({did: kakKeyId, options}); expect(key).to.eql({ From 79fa10ad7241b2d35f7edf60eb375450dc2e9a62 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 10 Aug 2022 14:02:54 -0400 Subject: [PATCH 17/44] Move vm related logic to ./lib/verificationMethods.js. --- lib/DidKeyDriver.js | 96 +++++++------------------------------- lib/validators.js | 6 --- lib/verificationMethods.js | 65 ++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 85 deletions(-) create mode 100644 lib/verificationMethods.js diff --git a/lib/DidKeyDriver.js b/lib/DidKeyDriver.js index ec49652..396a3a6 100644 --- a/lib/DidKeyDriver.js +++ b/lib/DidKeyDriver.js @@ -4,45 +4,12 @@ import * as didIo from '@digitalbazaar/did-io'; import {validateDidDocument, validateDidKey} from './validators.js'; import {DidResolverError} from '@digitalbazaar/did-io'; -import {Ed25519VerificationKey2018} from - '@digitalbazaar/ed25519-verification-key-2018'; -import { - Ed25519VerificationKey2020 -} from '@digitalbazaar/ed25519-verification-key-2020'; -import { - X25519KeyAgreementKey2019 -} from '@digitalbazaar/x25519-key-agreement-key-2019'; -import { - X25519KeyAgreementKey2020 -} from '@digitalbazaar/x25519-key-agreement-key-2020'; - const DID_CONTEXT_URL = 'https://www.w3.org/ns/did/v1'; -// For backwards compat only, not actually importing this suite -const ED25519_KEY_2018_CONTEXT_URL = - 'https://w3id.org/security/suites/ed25519-2018/v1'; - -const contextsBySuite = new Map([ - [Ed25519VerificationKey2020.suite, Ed25519VerificationKey2020.SUITE_CONTEXT], - ['Ed25519VerificationKey2018', ED25519_KEY_2018_CONTEXT_URL], - [X25519KeyAgreementKey2020.suite, X25519KeyAgreementKey2020.SUITE_CONTEXT], - [X25519KeyAgreementKey2019.suite, X25519KeyAgreementKey2019.SUITE_CONTEXT] -]); - -const VMsByKeyFormat = new Map([ - ['Ed25519VerificationKey2018', Ed25519VerificationKey2018], - ['Ed25519VerificationKey2020', Ed25519VerificationKey2020], - ['X25519KeyAgreementKey2019', X25519KeyAgreementKey2019], - ['X25519KeyAgreementKey2020', X25519KeyAgreementKey2020], - // this is an exception where we convert an ed key to json web key - ['JsonWebKey2020', Ed25519VerificationKey2020] -]); - -const defaultOptions = { - publicKeyFormat: 'Ed25519VerificationKey2020', - enableExperimentalPublicKeyTypes: false, - defaultContext: [DID_CONTEXT_URL], - enableEncryptionKeyDerivation: true -}; +import { + contextsBySuite, + getEncryptionMethod, + VMsByKeyFormat +} from './verificationMethods.js'; export class DidKeyDriver { /** @@ -226,14 +193,6 @@ export class DidKeyDriver { verificationKeyPair.controller = did; const contexts = [...defaultContext]; - - // The KAK pair will use the source key's controller, but will generate - // its own .id - const keyAgreementKeyPair = this._getEncryptionMethod({ - verificationKeyPair, - contexts - }); - // Now set the source key's id verificationKeyPair.id = `${did}#${verificationKeyPair.fingerprint()}`; @@ -252,7 +211,19 @@ export class DidKeyDriver { capabilityDelegation: [publicEdKey.id], capabilityInvocation: [publicEdKey.id] }; + // create the key pairs map + const keyPairs = new Map(); + keyPairs.set(verificationKeyPair.id, verificationKeyPair); + + // don't include an encryption verification method unless the option is true if(enableEncryptionKeyDerivation) { + // The KAK pair will use the source key's controller, but will generate + // its own .id + const keyAgreementKeyPair = getEncryptionMethod({ + verificationKeyPair, + contexts + }); + keyPairs.set(keyAgreementKeyPair.id, keyAgreementKeyPair); const publicDhKey = keyAgreementKeyPair.export({publicKey: true}); didDocument.keyAgreement = [publicDhKey]; } @@ -263,41 +234,9 @@ export class DidKeyDriver { enableEncryptionKeyDerivation } }); - // create the key pairs map - const keyPairs = new Map(); - keyPairs.set(verificationKeyPair.id, verificationKeyPair); - keyPairs.set(keyAgreementKeyPair.id, keyAgreementKeyPair); return {didDocument, keyPairs}; } - /** - * Gets the encryption method (which should be the keyAgreementKey). - * - * @param {object} options - Options to use. - * @param {object} options.verificationKeyPair - The verification key Pair. - * @param {Array} options.contexts - The contexts for the did - * document and keys. - * - * @returns {object} The encryption key. - */ - _getEncryptionMethod({verificationKeyPair, contexts}) { - if(verificationKeyPair.type === 'Ed25519VerificationKey2020') { - contexts.push(Ed25519VerificationKey2020.SUITE_CONTEXT, - X25519KeyAgreementKey2020.SUITE_CONTEXT); - return X25519KeyAgreementKey2020 - .fromEd25519VerificationKey2020({keyPair: verificationKeyPair}); - } else if(verificationKeyPair.type === 'Ed25519VerificationKey2018') { - contexts.push(ED25519_KEY_2018_CONTEXT_URL, - X25519KeyAgreementKey2019.SUITE_CONTEXT); - return X25519KeyAgreementKey2019 - .fromEd25519VerificationKey2018({keyPair: verificationKeyPair}); - } else { - throw new Error( - 'Cannot derive key agreement key from verification key type "' + - verificationKeyPair.type + '".' - ); - } - } _getVerificationMethod({publicKeyFormat = 'Ed25519VerificationKey2020'}) { const verificationMethod = this.verificationMethods.get(publicKeyFormat); if(!verificationMethod) { @@ -308,7 +247,6 @@ export class DidKeyDriver { } return verificationMethod; } - /** * Computes and returns the id of a given key pair. Used by `did-io` drivers. * diff --git a/lib/validators.js b/lib/validators.js index b24457f..c31c88f 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -37,20 +37,14 @@ export function validateDidKey({did}) { _validateVersion({version}); } -// FIXME while we do validate public key formats we do not -// convert to the requested public key format. /** * Public did:keys can be represented in multiple formats. * While we don't do any conversion in this library we still make * the check. * * @param {object} options - Options to use. - * @param {Array} options.publicKeyFormats - A list of - * public key formats our did:key implementation supports. * @param {object} options.didOptions - The didOptions from searchParams * and headers. - * @param {string} options.didOptions.publicKeyFormat - The format - * the public key should be returned in. * @param {string} options.didOptions.enableExperimentalPublicKeyTypes - An * option that can be passed in to allow experimental key types. * @param {boolean} options.didOptions.enableEncryptionKeyDerivation - If diff --git a/lib/verificationMethods.js b/lib/verificationMethods.js new file mode 100644 index 0000000..351f8d1 --- /dev/null +++ b/lib/verificationMethods.js @@ -0,0 +1,65 @@ +/*! + * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + */ + +import {Ed25519VerificationKey2018} from + '@digitalbazaar/ed25519-verification-key-2018'; +import { + Ed25519VerificationKey2020 +} from '@digitalbazaar/ed25519-verification-key-2020'; +import { + X25519KeyAgreementKey2019 +} from '@digitalbazaar/x25519-key-agreement-key-2019'; +import { + X25519KeyAgreementKey2020 +} from '@digitalbazaar/x25519-key-agreement-key-2020'; + +// For backwards compat only, not actually importing this suite +const ED25519_KEY_2018_CONTEXT_URL = + 'https://w3id.org/security/suites/ed25519-2018/v1'; + +export const contextsBySuite = new Map([ + [Ed25519VerificationKey2020.suite, Ed25519VerificationKey2020.SUITE_CONTEXT], + ['Ed25519VerificationKey2018', ED25519_KEY_2018_CONTEXT_URL], + [X25519KeyAgreementKey2020.suite, X25519KeyAgreementKey2020.SUITE_CONTEXT], + [X25519KeyAgreementKey2019.suite, X25519KeyAgreementKey2019.SUITE_CONTEXT] +]); + +export const VMsByKeyFormat = new Map([ + ['Ed25519VerificationKey2018', Ed25519VerificationKey2018], + ['Ed25519VerificationKey2020', Ed25519VerificationKey2020], + ['X25519KeyAgreementKey2019', X25519KeyAgreementKey2019], + ['X25519KeyAgreementKey2020', X25519KeyAgreementKey2020], + // this is an exception where we convert an ed key to json web key + ['JsonWebKey2020', Ed25519VerificationKey2020] +]); + +/** + * Gets the encryption method (which should be the keyAgreementKey). + * + * @param {object} options - Options to use. + * @param {object} options.verificationKeyPair - The verification key Pair. + * @param {Array} options.contexts - The contexts for the did + * document and keys. + * + * @returns {object} The encryption key. + */ +export function getEncryptionMethod({verificationKeyPair, contexts}) { + if(verificationKeyPair.type === 'Ed25519VerificationKey2020') { + contexts.push(Ed25519VerificationKey2020.SUITE_CONTEXT, + X25519KeyAgreementKey2020.SUITE_CONTEXT); + return X25519KeyAgreementKey2020 + .fromEd25519VerificationKey2020({keyPair: verificationKeyPair}); + } else if(verificationKeyPair.type === 'Ed25519VerificationKey2018') { + contexts.push(ED25519_KEY_2018_CONTEXT_URL, + X25519KeyAgreementKey2019.SUITE_CONTEXT); + return X25519KeyAgreementKey2019 + .fromEd25519VerificationKey2018({keyPair: verificationKeyPair}); + } else { + throw new Error( + 'Cannot derive key agreement key from verification key type "' + + verificationKeyPair.type + '".' + ); + } +} + From 2a8affe31005e20d7e74456697781492d3160b6e Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 10 Aug 2022 14:15:36 -0400 Subject: [PATCH 18/44] Remove didKeyDriver2018 from tests & add noKaK test. --- test/driver.spec.js | 17 +++++++++++------ test/expected-data.js | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/test/driver.spec.js b/test/driver.spec.js index 8e477a5..a73c218 100644 --- a/test/driver.spec.js +++ b/test/driver.spec.js @@ -3,6 +3,7 @@ */ import chai from 'chai'; import {driver} from '../lib/index.js'; +import {noKaKDidDoc} from './expected-data.js'; import {Ed25519VerificationKey2020} from '@digitalbazaar/ed25519-verification-key-2020'; @@ -55,14 +56,13 @@ describe('did:key method driver', () => { it('should get the DID Doc in with publicKeyFormat ' + 'Ed25519VerificationKey2018', async () => { - const didKeyDriver2018 = driver(); // Note: Testing same keys as previous (2020 mode) test const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; const options = { publicKeyFormat: 'Ed25519VerificationKey2018', enableExperimentalPublicKeyTypes: true }; - const didDocument = await didKeyDriver2018.get({did, options}); + const didDocument = await didKeyDriver.get({did, options}); const expectedDidDoc = { '@context': [ @@ -129,14 +129,13 @@ describe('did:key method driver', () => { it('should resolve an individual key with publicKeyFormat ' + 'Ed25519VerificationKey2018', async () => { - const didKeyDriver2018 = driver(); const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; const keyId = did + '#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; const options = { publicKeyFormat: 'Ed25519VerificationKey2018', enableExperimentalPublicKeyTypes: true }; - const key = await didKeyDriver2018.get({did: keyId, options}); + const key = await didKeyDriver.get({did: keyId, options}); expect(key).to.eql({ '@context': 'https://w3id.org/security/suites/ed25519-2018/v1', @@ -164,9 +163,15 @@ describe('did:key method driver', () => { }); }); + it('should resolve a DID doc with out an encryption method', async () => { + const did = 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T'; + const options = {enableEncryptionKeyDerivation: false}; + const key = await didKeyDriver.get({did, options}); + expect(key).to.eql(noKaKDidDoc); + }); + it('should resolve an individual key agreement key using publicKeyFormat ' + 'Ed25519VerificationKey2018', async () => { - const didKeyDriver2018 = driver(); const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; const kakKeyId = `${did}#z6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc`; @@ -174,7 +179,7 @@ describe('did:key method driver', () => { publicKeyFormat: 'Ed25519VerificationKey2018', enableExperimentalPublicKeyTypes: true }; - const key = await didKeyDriver2018.get({did: kakKeyId, options}); + const key = await didKeyDriver.get({did: kakKeyId, options}); expect(key).to.eql({ '@context': 'https://w3id.org/security/suites/x25519-2019/v1', diff --git a/test/expected-data.js b/test/expected-data.js index fb7c904..af84c25 100644 --- a/test/expected-data.js +++ b/test/expected-data.js @@ -36,3 +36,30 @@ export const expectedDidDoc = { ] }; /* eslint-enable */ + +export const noKaKDidDoc = { + '@context': [ + 'https://www.w3.org/ns/did/v1' + ], + assertionMethod: [ + 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T#z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T' + ], + authentication: [ + 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T#z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T' + ], + capabilityDelegation: [ + 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T#z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T' + ], + capabilityInvocation: [ + 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T#z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T' + ], + id: 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T', + verificationMethod: [ + { + controller: 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T', + id: 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T#z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T', + publicKeyMultibase: 'z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T', + type: 'Ed25519VerificationKey2020' + } + ] +}; From add28ac37bb078d1affd9e133193947ccd0741c2 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 10 Aug 2022 14:17:36 -0400 Subject: [PATCH 19/44] Lint and disable linter in expected didDoc file. --- test/driver.spec.js | 2 +- test/expected-data.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/driver.spec.js b/test/driver.spec.js index a73c218..4959dbc 100644 --- a/test/driver.spec.js +++ b/test/driver.spec.js @@ -3,9 +3,9 @@ */ import chai from 'chai'; import {driver} from '../lib/index.js'; -import {noKaKDidDoc} from './expected-data.js'; import {Ed25519VerificationKey2020} from '@digitalbazaar/ed25519-verification-key-2020'; +import {noKaKDidDoc} from './expected-data.js'; chai.should(); const {expect} = chai; diff --git a/test/expected-data.js b/test/expected-data.js index af84c25..d3e62b1 100644 --- a/test/expected-data.js +++ b/test/expected-data.js @@ -35,7 +35,6 @@ export const expectedDidDoc = { } ] }; -/* eslint-enable */ export const noKaKDidDoc = { '@context': [ @@ -63,3 +62,6 @@ export const noKaKDidDoc = { } ] }; + +/* eslint-enable */ + From dc6089d581e50b881bd558950ab0427862e9aa4a Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 10 Aug 2022 16:27:15 -0400 Subject: [PATCH 20/44] Move ed25519 verification key 2018 to deps. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 01797b4..e2dd0b2 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,12 @@ ], "dependencies": { "@digitalbazaar/did-io": "digitalbazaar/did-io#add-did-key-spec-7-validators", + "@digitalbazaar/ed25519-verification-key-2018": "^4.0.0", "@digitalbazaar/ed25519-verification-key-2020": "^4.1.0", "@digitalbazaar/x25519-key-agreement-key-2019": "^6.0.0", "@digitalbazaar/x25519-key-agreement-key-2020": "^3.0.0" }, "devDependencies": { - "@digitalbazaar/ed25519-verification-key-2018": "^4.0.0", "c8": "^7.11.3", "chai": "^4.3.6", "cross-env": "^7.0.3", From 23173d5c42e19fed59079c14d04ac05a61b247dc Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 10 Aug 2022 17:06:15 -0400 Subject: [PATCH 21/44] Use better naming convention for map of verification methods. --- lib/DidKeyDriver.js | 4 ++-- lib/verificationMethods.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/DidKeyDriver.js b/lib/DidKeyDriver.js index 396a3a6..e988406 100644 --- a/lib/DidKeyDriver.js +++ b/lib/DidKeyDriver.js @@ -8,7 +8,7 @@ const DID_CONTEXT_URL = 'https://www.w3.org/ns/did/v1'; import { contextsBySuite, getEncryptionMethod, - VMsByKeyFormat + methodsByKeyFormat } from './verificationMethods.js'; export class DidKeyDriver { @@ -18,7 +18,7 @@ export class DidKeyDriver { * A map of verification methods with the key as the publicKeyFormat. */ constructor({ - verificationMethods = VMsByKeyFormat, + verificationMethods = methodsByKeyFormat, } = {}) { // used by did-io to register drivers this.method = 'key'; diff --git a/lib/verificationMethods.js b/lib/verificationMethods.js index 351f8d1..77f89fa 100644 --- a/lib/verificationMethods.js +++ b/lib/verificationMethods.js @@ -25,7 +25,7 @@ export const contextsBySuite = new Map([ [X25519KeyAgreementKey2019.suite, X25519KeyAgreementKey2019.SUITE_CONTEXT] ]); -export const VMsByKeyFormat = new Map([ +export const methodsByKeyFormat = new Map([ ['Ed25519VerificationKey2018', Ed25519VerificationKey2018], ['Ed25519VerificationKey2020', Ed25519VerificationKey2020], ['X25519KeyAgreementKey2019', X25519KeyAgreementKey2019], From 39ced6bd3d67515f48ea52ba3d4fc07052ba4d6a Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 10 Aug 2022 17:19:16 -0400 Subject: [PATCH 22/44] Add check for representationNotSupported. --- lib/DidKeyDriver.js | 9 ++++++++- lib/validators.js | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/DidKeyDriver.js b/lib/DidKeyDriver.js index e988406..e17040c 100644 --- a/lib/DidKeyDriver.js +++ b/lib/DidKeyDriver.js @@ -2,7 +2,11 @@ * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ import * as didIo from '@digitalbazaar/did-io'; -import {validateDidDocument, validateDidKey} from './validators.js'; +import { + validateDidDocument, + validateDidKey, + validatePublicKeyFormat +} from './validators.js'; import {DidResolverError} from '@digitalbazaar/did-io'; const DID_CONTEXT_URL = 'https://www.w3.org/ns/did/v1'; import { @@ -238,7 +242,10 @@ export class DidKeyDriver { return {didDocument, keyPairs}; } _getVerificationMethod({publicKeyFormat = 'Ed25519VerificationKey2020'}) { + // ensure public key format is supported by did:key at this time + validatePublicKeyFormat({publicKeyFormat}); const verificationMethod = this.verificationMethods.get(publicKeyFormat); + // if there is no verificationMethod for a format it is unsupported if(!verificationMethod) { throw new DidResolverError({ message: `Unsupported public key type ${publicKeyFormat}`, diff --git a/lib/validators.js b/lib/validators.js index c31c88f..edaad42 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -3,6 +3,18 @@ */ import {DidResolverError} from '@digitalbazaar/did-io'; +export function validatePublicKeyFormat({publicKeyFormat}) { + // FIXME add JsonWebKey2020 support + // FIXME add Multikey support once Multikey spec and context are finished + const notSupported = new Set(['Multikey', 'JsonWebKey2020']); + if(notSupported.has(publicKeyFormat)) { + throw new DidResolverError({ + message: `Representation NotSupported ${publicKeyFormat}`, + code: 'representationNotSupported' + }); + } +} + /** * General validation for did:keys independent * of key type specific validation. From 75c2b16ffd3808142f2b4369abacbc4665c9434b Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 10 Aug 2022 17:25:27 -0400 Subject: [PATCH 23/44] Clarify rationale behind representationNotSupported & unknownPublicKeyType. --- lib/DidKeyDriver.js | 5 +++-- lib/validators.js | 12 ++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/DidKeyDriver.js b/lib/DidKeyDriver.js index e17040c..58a996f 100644 --- a/lib/DidKeyDriver.js +++ b/lib/DidKeyDriver.js @@ -242,10 +242,11 @@ export class DidKeyDriver { return {didDocument, keyPairs}; } _getVerificationMethod({publicKeyFormat = 'Ed25519VerificationKey2020'}) { - // ensure public key format is supported by did:key at this time + // ensure public key format is a format we have an implementation of validatePublicKeyFormat({publicKeyFormat}); const verificationMethod = this.verificationMethods.get(publicKeyFormat); - // if there is no verificationMethod for a format it is unsupported + // if there is no verificationMethod then the method is not supported by + // the `did:key` spec if(!verificationMethod) { throw new DidResolverError({ message: `Unsupported public key type ${publicKeyFormat}`, diff --git a/lib/validators.js b/lib/validators.js index edaad42..1f2f541 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -3,6 +3,18 @@ */ import {DidResolverError} from '@digitalbazaar/did-io'; +/** + * Throws if the publicKeyFormat is in a format that this library + * does not have an implementation for. + * + * @param {object} options - Options to use. + * @param {string} options.publicKeyFormat - The publicKeyFormat. + * + * @throws {DidResolverError} If there is no supporting library + * for the public key format. + * + * @returns {undefined} Returns on success. + */ export function validatePublicKeyFormat({publicKeyFormat}) { // FIXME add JsonWebKey2020 support // FIXME add Multikey support once Multikey spec and context are finished From 3c969b5b71cc2165f43586241e4ce24a26a01e3a Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 12 Aug 2022 08:13:48 -0400 Subject: [PATCH 24/44] Update CHANGELOG for 4.0 release. --- CHANGELOG.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f440cdf..bcc8b14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,17 @@ ## 4.0.0 - ### Added -- **BREAKING**: Implemented validators in did resolver. -- **BREAKING**: Added added validators for didDocument creation. -- Added options for didDocument creation. +- **BREAKING**: Added validators for didDocument creation. +- Added a `DidKeyDriver` options parameter to `didKeyDriver.{get, publicKeyToDidDoc, generate}`. +- Added option `publicKeyFormat` to `DidKeyDriver` options. +- Added option `enableEncryptionKeyDerivation` to `DidKeyDriver` options. +- Added option `enableExperimentalPublicKeyTypes` to `DidKeyDriver` options. + +### Changed +- **BREAKING**: `DidKeyDriver` now accepts a Map of `verificationMethods` in the constructor. + +### Removed +- **BREAKING**: `DidKeyDriver` no longer takes a `verificationSuite` in the constructor. ## 3.0.0 - 2022-06-02 From e03957f82f72c4d75fb9b3f75062fcd47a55ac96 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 12 Aug 2022 10:47:35 -0400 Subject: [PATCH 25/44] Add --full-trace & --check-leaks to test options. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e2dd0b2..749b25b 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ ], "scripts": { "test": "npm run test-node", - "test-node": "cross-env NODE_ENV=test mocha --preserve-symlinks -t 10000 test/*.spec.js", + "test-node": "cross-env NODE_ENV=test mocha --preserve-symlinks --full-trace --check-leaks -t 10000 test/*.spec.js", "test-karma": "karma start karma.conf.cjs", "coverage": "cross-env NODE_ENV=test c8 npm run test-node", "coverage-ci": "cross-env NODE_ENV=test c8 --reporter=lcovonly --reporter=text-summary --reporter=text npm run test-node", From e94064a046c872cef9097cb9eaeda349f273bf02 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 12 Aug 2022 13:40:14 -0400 Subject: [PATCH 26/44] Add tests for Multikey, JsonWebKey2020, & experimental types false. --- test/driver.spec.js | 72 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/test/driver.spec.js b/test/driver.spec.js index 4959dbc..7e0436c 100644 --- a/test/driver.spec.js +++ b/test/driver.spec.js @@ -2,6 +2,7 @@ * Copyright (c) 2019-20201 Digital Bazaar, Inc. All rights reserved. */ import chai from 'chai'; +import {DidResolverError} from '@digitalbazaar/did-io'; import {driver} from '../lib/index.js'; import {Ed25519VerificationKey2020} from '@digitalbazaar/ed25519-verification-key-2020'; @@ -53,7 +54,78 @@ describe('did:key method driver', () => { expect(kak.publicKeyMultibase).to .equal('z6LSotGbgPCJD2Y6TSvvgxERLTfVZxCh9KSrez3WNrNp7vKW'); }); + it('should throw representationNotSupported if publicKeyFormat is Multikey', + async () => { + const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; + const options = {publicKeyFormat: 'Multikey'}; + let error; + let didDocument; + try { + didDocument = await didKeyDriver.get({did, options}); + } catch(e) { + error = e; + } + expect( + didDocument, + 'Expected driver to throw not return a didDocument' + ).to.not.exist; + expect(error).to.exist; + expect(error).to.be.an.instanceof( + DidResolverError, + 'Expected a DidResolverError' + ); + expect(error.code).to.equal('representationNotSupported'); + }); + it('should throw representationNotSupported if publicKeyFormat is ' + + 'JsonWebKey2020', async () => { + const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; + const options = {publicKeyFormat: 'JsonWebKey2020'}; + let error; + let didDocument; + try { + didDocument = await didKeyDriver.get({did, options}); + } catch(e) { + error = e; + } + expect( + didDocument, + 'Expected driver to throw not return a didDocument' + ).to.not.exist; + expect(error).to.exist; + expect(error).to.be.an.instanceof( + DidResolverError, + 'Expected a DidResolverError' + ); + expect(error.code).to.equal('representationNotSupported'); + }); + it('should throw invalidPublicKeyType if publicKeyFormat is experimental' + + ' & enableExperimentalPublicKeyTypes is false', async () => { + // Note: Testing same keys as previous (2020 mode) test + const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; + const options = { + publicKeyFormat: 'Ed25519VerificationKey2018', + // this is the default value of false + enableExperimentalPublicKeyTypes: false + }; + let error; + let didDocument; + try { + didDocument = await didKeyDriver.get({did, options}); + } catch(e) { + error = e; + } + expect( + didDocument, + 'Expected driver to throw not return a didDocument' + ).to.not.exist; + expect(error).to.exist; + expect(error).to.be.an.instanceof( + DidResolverError, + 'Expected a DidResolverError' + ); + expect(error.code).to.equal('invalidPublicKeyType'); + }); it('should get the DID Doc in with publicKeyFormat ' + 'Ed25519VerificationKey2018', async () => { // Note: Testing same keys as previous (2020 mode) test From 7bb57e80468403e2c9eaccdea0b965ac85e762c3 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 12 Aug 2022 13:52:59 -0400 Subject: [PATCH 27/44] Add tests for scheme, method, and multibase. --- test/driver.spec.js | 62 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/test/driver.spec.js b/test/driver.spec.js index 7e0436c..861f3d2 100644 --- a/test/driver.spec.js +++ b/test/driver.spec.js @@ -54,6 +54,68 @@ describe('did:key method driver', () => { expect(kak.publicKeyMultibase).to .equal('z6LSotGbgPCJD2Y6TSvvgxERLTfVZxCh9KSrez3WNrNp7vKW'); }); + it('should throw invalidDid if scheme is not did', async () => { + const did = 'key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; + let error; + let didDocument; + try { + didDocument = await didKeyDriver.get({did}); + } catch(e) { + error = e; + } + expect( + didDocument, + 'Expected driver to throw not return a didDocument' + ).to.not.exist; + expect(error).to.exist; + expect(error).to.be.an.instanceof( + DidResolverError, + 'Expected a DidResolverError' + ); + expect(error.code).to.equal('invalidDid'); + }); + it('should throw invalidDid if method is not key', async () => { + const did = 'did:notKey:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; + let error; + let didDocument; + try { + didDocument = await didKeyDriver.get({did}); + } catch(e) { + error = e; + } + expect( + didDocument, + 'Expected driver to throw not return a didDocument' + ).to.not.exist; + expect(error).to.exist; + expect(error).to.be.an.instanceof( + DidResolverError, + 'Expected a DidResolverError' + ); + expect(error.code).to.equal('invalidDid'); + }); + it('should throw invalidDid if identifier doesn\'t begin with z', + async () => { + const did = 'did:key:6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; + let error; + let didDocument; + try { + didDocument = await didKeyDriver.get({did}); + } catch(e) { + error = e; + } + expect( + didDocument, + 'Expected driver to throw not return a didDocument' + ).to.not.exist; + expect(error).to.exist; + expect(error).to.be.an.instanceof( + DidResolverError, + 'Expected a DidResolverError' + ); + expect(error.code).to.equal('invalidDid'); + }); + it('should throw representationNotSupported if publicKeyFormat is Multikey', async () => { const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; From 4f14324eca40fb051eeff9ac22c47c7d2d39794f Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 12 Aug 2022 16:10:15 -0400 Subject: [PATCH 28/44] Add tests for version number in did:key. --- lib/DidKeyDriver.js | 4 ++-- test/driver.spec.js | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/DidKeyDriver.js b/lib/DidKeyDriver.js index 58a996f..643c6e6 100644 --- a/lib/DidKeyDriver.js +++ b/lib/DidKeyDriver.js @@ -3,6 +3,7 @@ */ import * as didIo from '@digitalbazaar/did-io'; import { + parseDidKey, validateDidDocument, validateDidKey, validatePublicKeyFormat @@ -131,8 +132,7 @@ export class DidKeyDriver { validateDidKey({did}); const [didAuthority, keyIdFragment] = did.split('#'); - - const fingerprint = didAuthority.substr('did:key:'.length); + const {multibase: fingerprint} = parseDidKey({did: didAuthority}); const keyPair = this._getVerificationMethod(options). fromFingerprint({fingerprint}); diff --git a/test/driver.spec.js b/test/driver.spec.js index 861f3d2..bc6b060 100644 --- a/test/driver.spec.js +++ b/test/driver.spec.js @@ -115,6 +115,42 @@ describe('did:key method driver', () => { ); expect(error.code).to.equal('invalidDid'); }); + it('should get a didDocument from a did with a version', + async () => { + const did = 'did:key:3:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpM' + + 'vtuVMy53T'; + let error; + let didDocument; + try { + didDocument = await didKeyDriver.get({did}); + } catch(e) { + error = e; + } + expect(didDocument, 'Expected driver to return a didDocument').exist; + expect(error).to.not.exist; + }); + it('should throw invalidDid if version is negative', + async () => { + const did = 'did:key:-3:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpM' + + 'vtuVMy53T'; + let error; + let didDocument; + try { + didDocument = await didKeyDriver.get({did}); + } catch(e) { + error = e; + } + expect( + didDocument, + 'Expected driver to return a didDocument' + ).not.exist; + expect(error).to.exist; + expect(error).to.be.an.instanceof( + DidResolverError, + 'Expected a DidResolverError' + ); + expect(error.code).to.equal('invalidDid'); + }); it('should throw representationNotSupported if publicKeyFormat is Multikey', async () => { From 56f551aa81ff0dda6a7a904fd6939abae60b5240 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 12 Aug 2022 16:36:45 -0400 Subject: [PATCH 29/44] Test corrections. --- lib/validators.js | 26 +++++++++++++------------- test/driver.spec.js | 5 ++--- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/validators.js b/lib/validators.js index 1f2f541..60681f3 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -114,18 +114,18 @@ export function validateDidDocument({ * @returns {undefined} Returns on success. */ function _validateVersion({version}) { - try { - const versionNumber = Number.parseInt(version); - if(versionNumber <= 0) { - throw new DidResolverError({ - message: 'Version must be a positive integer received ' + - `"${versionNumber}"`, - code: 'invalidDid' - }); - } - } catch(e) { + const versionNumber = Number.parseInt(version); + if(Number.isNaN(versionNumber)) { + throw new DidResolverError({ + message: 'Version must be a positive integer received ' + + `"${version}"`, + code: 'invalidDid' + }); + } + if(versionNumber <= 0) { throw new DidResolverError({ - message: `Version must be an integer received "${version}"`, + message: 'Version must be a positive integer received ' + + `"${version}"`, code: 'invalidDid' }); } @@ -150,8 +150,8 @@ export function parseDidKey({did}) { // so we can correctly parse the identifier after the method function _getBaseDidKey({did}) { const pchar = '[a-zA-Z0-9\\-\\._~]|%[0-9a-fA-F]{2}|[!$&\'()*+,;=:@]'; - const didKeyPattern = '(?did):(?key):?(?\\d*)' + - `:(?z(${pchar})+)`; + const didKeyPattern = '(?[a-z0-9]+):(?[a-z0-9]+)' + + `(:(?\\d+))?:(?z(${pchar})+)`; // this can return null if scheme, method, and multibase are not defined const {groups = {}} = (new RegExp(didKeyPattern).exec(did) || {}); return groups; diff --git a/test/driver.spec.js b/test/driver.spec.js index bc6b060..981d9d3 100644 --- a/test/driver.spec.js +++ b/test/driver.spec.js @@ -55,7 +55,7 @@ describe('did:key method driver', () => { .equal('z6LSotGbgPCJD2Y6TSvvgxERLTfVZxCh9KSrez3WNrNp7vKW'); }); it('should throw invalidDid if scheme is not did', async () => { - const did = 'key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; + const did = 'notdid:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; let error; let didDocument; try { @@ -75,7 +75,7 @@ describe('did:key method driver', () => { expect(error.code).to.equal('invalidDid'); }); it('should throw invalidDid if method is not key', async () => { - const did = 'did:notKey:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; + const did = 'did:notkey:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; let error; let didDocument; try { @@ -151,7 +151,6 @@ describe('did:key method driver', () => { ); expect(error.code).to.equal('invalidDid'); }); - it('should throw representationNotSupported if publicKeyFormat is Multikey', async () => { const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; From 877883b21cd67ef3412a5eb20316a58a8cdc412e Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 12 Aug 2022 16:47:06 -0400 Subject: [PATCH 30/44] Add test for unsupportedPublicKeyType. --- lib/validators.js | 2 +- test/driver.spec.js | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/validators.js b/lib/validators.js index 60681f3..2b3afe6 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -150,7 +150,7 @@ export function parseDidKey({did}) { // so we can correctly parse the identifier after the method function _getBaseDidKey({did}) { const pchar = '[a-zA-Z0-9\\-\\._~]|%[0-9a-fA-F]{2}|[!$&\'()*+,;=:@]'; - const didKeyPattern = '(?[a-z0-9]+):(?[a-z0-9]+)' + + const didKeyPattern = '(?[a-z]+):(?[a-z0-9]+)' + `(:(?\\d+))?:(?z(${pchar})+)`; // this can return null if scheme, method, and multibase are not defined const {groups = {}} = (new RegExp(didKeyPattern).exec(did) || {}); diff --git a/test/driver.spec.js b/test/driver.spec.js index 981d9d3..c859803 100644 --- a/test/driver.spec.js +++ b/test/driver.spec.js @@ -195,7 +195,28 @@ describe('did:key method driver', () => { ); expect(error.code).to.equal('representationNotSupported'); }); - + it('should throw unsupportedPublicKeyType if publicKeyFormat is unknown', + async () => { + const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; + const options = {publicKeyFormat: 'UnknownFormat2020'}; + let error; + let didDocument; + try { + didDocument = await didKeyDriver.get({did, options}); + } catch(e) { + error = e; + } + expect( + didDocument, + 'Expected driver to throw not return a didDocument' + ).to.not.exist; + expect(error).to.exist; + expect(error).to.be.an.instanceof( + DidResolverError, + 'Expected a DidResolverError' + ); + expect(error.code).to.equal('unsupportedPublicKeyType'); + }); it('should throw invalidPublicKeyType if publicKeyFormat is experimental' + ' & enableExperimentalPublicKeyTypes is false', async () => { // Note: Testing same keys as previous (2020 mode) test From 048e6952488710923fc44c79bae02b6609727026 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 12 Aug 2022 16:49:37 -0400 Subject: [PATCH 31/44] Correct test title. --- test/driver.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/driver.spec.js b/test/driver.spec.js index c859803..c424b06 100644 --- a/test/driver.spec.js +++ b/test/driver.spec.js @@ -244,7 +244,7 @@ describe('did:key method driver', () => { ); expect(error.code).to.equal('invalidPublicKeyType'); }); - it('should get the DID Doc in with publicKeyFormat ' + + it('should get the DID Doc with publicKeyFormat ' + 'Ed25519VerificationKey2018', async () => { // Note: Testing same keys as previous (2020 mode) test const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; From ad51d3d7796b126acb1790901d82e2cad80f6a63 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 12 Aug 2022 16:50:37 -0400 Subject: [PATCH 32/44] Drop node 14 from ci actions. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7a0d85f..6a6bc6e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - node-version: [14.x, 16.x, 18.x] + node-version: [16.x, 18.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} From 49e302a92c143a7ad97134d8c1e09b96b288bcaa Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 17 Aug 2022 16:23:55 -0400 Subject: [PATCH 33/44] Add README section for Ed25519VerificationKey2018. --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 76f8602..5c1e081 100644 --- a/README.md +++ b/README.md @@ -254,17 +254,17 @@ If you need DID Documents that are using the 2018/2019 crypto suites, you can customize the driver as follows. ```js -import { - Ed25519VerificationKey2018 -} from '@digitalbazaar/ed25519-verification-key-2018'; import * as didKey from '@digitalbazaar/did-method-key'; -const didKeyDriver2018 = didKey.driver({ - verificationSuite: Ed25519VerificationKey2018 -}); +const didKeyDriver = didKey.driver(); const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; -await didKeyDriver2018.get({did}); +const options = { + publicKeyFormat: 'Ed25519VerificationKey2018', + // this is the default value of false + enableExperimentalPublicKeyTypes: false +}; +await didKeyDriver.get({did, options}); // -> { '@context': [ From df187bce4499b40a1c3eed0a459cb30a42eafd9f Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 18 Aug 2022 10:23:23 -0400 Subject: [PATCH 34/44] Add README docs for options. --- README.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5c1e081..0f4c884 100644 --- a/README.md +++ b/README.md @@ -184,9 +184,26 @@ To get a DID Document for an existing `did:key` DID: const did = 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T'; const didDocument = await didKeyDriver.get({did}); ``` - (Results in the [example DID Doc](#example-did-document) above). +### options for `get()`, `publicKeyToDidDoc`, & `generate` +`get`, `publicKeyToDidDoc`, and `generate` both take an options object with the following options: + +```js +const options = { + // default publicKeyFormat + publicKeyFormat: 'Ed25519VerificationKey2020', + // this defaults to false + enableExperimentalPublicKeyTypes: false, + // the context for the resulting did document + // the default is the did context + defaultContext = [DID_CONTEXT_URL], + // if false no keyAgreementKey is included + // defaults to true + enableEncryptionKeyDerivation = true +}; +``` + #### Getting just the key object by key id You can also use a `.get()` to retrieve an individual key, if you know it's id @@ -261,8 +278,8 @@ const didKeyDriver = didKey.driver(); const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH'; const options = { publicKeyFormat: 'Ed25519VerificationKey2018', - // this is the default value of false - enableExperimentalPublicKeyTypes: false + // this defaults to false + enableExperimentalPublicKeyTypes: true }; await didKeyDriver.get({did, options}); // -> From 5696f2e5974fb15d7364866ca53711815c48971e Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 18 Aug 2022 10:24:55 -0400 Subject: [PATCH 35/44] Add example of using options with get. --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f4c884..01e4571 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ const didDocument = await didKeyDriver.get({did}); ``` (Results in the [example DID Doc](#example-did-document) above). -### options for `get()`, `publicKeyToDidDoc`, & `generate` +### options for `get`, `publicKeyToDidDoc`, & `generate` `get`, `publicKeyToDidDoc`, and `generate` both take an options object with the following options: ```js @@ -202,6 +202,9 @@ const options = { // defaults to true enableEncryptionKeyDerivation = true }; + +const did = 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T'; +const didDoc = await didKeyDriver.get({did, options}); ``` #### Getting just the key object by key id From 9904378981a218fbfaa7dbe552fade454bac3c73 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 18 Aug 2022 10:39:36 -0400 Subject: [PATCH 36/44] Set all defaults in one place & improve jsdoc. --- CHANGELOG.md | 2 ++ lib/DidKeyDriver.js | 17 +++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcc8b14..fc5d3f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ - Added option `publicKeyFormat` to `DidKeyDriver` options. - Added option `enableEncryptionKeyDerivation` to `DidKeyDriver` options. - Added option `enableExperimentalPublicKeyTypes` to `DidKeyDriver` options. +- Added option `defaultContext` to `DidKeyDriver` options. + ### Changed - **BREAKING**: `DidKeyDriver` now accepts a Map of `verificationMethods` in the constructor. diff --git a/lib/DidKeyDriver.js b/lib/DidKeyDriver.js index 643c6e6..b37103e 100644 --- a/lib/DidKeyDriver.js +++ b/lib/DidKeyDriver.js @@ -19,7 +19,7 @@ import { export class DidKeyDriver { /** * @param {object} options - Options to use. - * @param {Map} [options.verificationMethods=VMsByKeyFormat] - + * @param {Map} [options.verificationMethods] - * A map of verification methods with the key as the publicKeyFormat. */ constructor({ @@ -37,7 +37,7 @@ export class DidKeyDriver { * @param {object} options - Options hashmap. * @param {Uint8Array} [options.seed] - A 32-byte array seed for a * deterministic key. - * @param {object} [options.options] - Did Document creation options. + * @param {object} [options.options] - DID Document creation options. * * @returns {Promise<{didDocument: object, keyPairs: Map, * methodFor: Function}>} Resolves with the generated DID Document, along @@ -119,12 +119,12 @@ export class DidKeyDriver { * or an x25519 key-agreement key id). * @param {string} [options.url] - Alias for the `did` url param, supported * for better readability of invoking code. - * @param {object} [options.options = {}] - Options for didDocument creation. + * @param {object} [options.options] - Options for didDocument creation. * * @returns {Promise} Resolves to a DID Document or a * public key node with context. */ - async get({did, url, options = {}} = {}) { + async get({did, url, options} = {}) { did = did || url; if(!did) { throw new TypeError('"did" must be a string.'); @@ -177,8 +177,7 @@ export class DidKeyDriver { * @param {LDKeyPair|object} options.keyPair - Key used to generate the DID * document (either an LDKeyPair instance containing public key material, * or a "key description" plain object (such as that generated from a KMS)). - * @param {object} [options.options = defaultOptions] - Options for - * didDocument creation. + * @param {object} [options.options = {}] - Options for didDocument creation. * * @returns {Promise<{didDocument: object, keyPairs: Map}>} * Resolves with the generated DID Document, along with the corresponding @@ -186,7 +185,7 @@ export class DidKeyDriver { */ async _keyPairToDidDocument({keyPair, options = {}} = {}) { const { - publicKeyFormat, + publicKeyFormat = 'Ed25519VerificationKey2020', enableExperimentalPublicKeyTypes = false, defaultContext = [DID_CONTEXT_URL], enableEncryptionKeyDerivation = true @@ -241,7 +240,9 @@ export class DidKeyDriver { return {didDocument, keyPairs}; } - _getVerificationMethod({publicKeyFormat = 'Ed25519VerificationKey2020'}) { + _getVerificationMethod({ + publicKeyFormat = 'Ed25519VerificationKey2020' + } = {}) { // ensure public key format is a format we have an implementation of validatePublicKeyFormat({publicKeyFormat}); const verificationMethod = this.verificationMethods.get(publicKeyFormat); From 2d7fdf300f0a0efecf4058c24d88b2e39ee232ab Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 18 Aug 2022 10:51:37 -0400 Subject: [PATCH 37/44] Replace hashmap w/ object in comments & fix driver creation function. --- lib/DidKeyDriver.js | 14 +++++++------- lib/index.js | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/DidKeyDriver.js b/lib/DidKeyDriver.js index b37103e..194a32e 100644 --- a/lib/DidKeyDriver.js +++ b/lib/DidKeyDriver.js @@ -34,7 +34,7 @@ export class DidKeyDriver { * Generates a new `did:key` method DID Document (optionally, from a * deterministic seed value). * - * @param {object} options - Options hashmap. + * @param {object} options - Options object. * @param {Uint8Array} [options.seed] - A 32-byte array seed for a * deterministic key. * @param {object} [options.options] - DID Document creation options. @@ -81,7 +81,7 @@ export class DidKeyDriver { * const authPublicKey = await cryptoLd.from(authKeyData); * const {verify} = authPublicKey.verifier(); * - * @param {object} options - Options hashmap. + * @param {object} options - Options object. * @param {object} options.didDocument - DID Document (retrieved via a * `.get()` or from some other source). * @param {string} options.purpose - Verification method purpose, such as @@ -114,7 +114,7 @@ export class DidKeyDriver { * await resolver.get({did}); // -> did document * await resolver.get({url: keyId}); // -> public key node * - * @param {object} options - Options hashmap. + * @param {object} options - Options object. * @param {string} [options.did] - DID URL or a key id (either an ed25519 key * or an x25519 key-agreement key id). * @param {string} [options.url] - Alias for the `did` url param, supported @@ -152,7 +152,7 @@ export class DidKeyDriver { * Note that unlike `generate()`, a `keyPairs` map is not returned. Use * `publicMethodFor()` to fetch keys for particular proof purposes. * - * @param {object} options - Options hashmap. + * @param {object} options - Options object. * @typedef LDKeyPair * @param {LDKeyPair|object} options.publicKeyDescription - Public key object * used to generate the DID document (either an LDKeyPair instance @@ -173,7 +173,7 @@ export class DidKeyDriver { /** * Converts an Ed25519KeyPair object to a `did:key` method DID Document. * - * @param {object} options - Options hashmap. + * @param {object} options - Options object. * @param {LDKeyPair|object} options.keyPair - Key used to generate the DID * document (either an LDKeyPair instance containing public key material, * or a "key description" plain object (such as that generated from a KMS)). @@ -259,7 +259,7 @@ export class DidKeyDriver { /** * Computes and returns the id of a given key pair. Used by `did-io` drivers. * - * @param {object} options - Options hashmap. + * @param {object} options - Options object. * @param {LDKeyPair} options.keyPair - The key pair used when computing the * identifier. * @@ -273,7 +273,7 @@ export class DidKeyDriver { /** * Returns the public key object for a given key id fragment. * - * @param {object} options - Options hashmap. + * @param {object} options - Options object. * @param {object} options.didDocument - The DID Document to use when generating * the id. * @param {string} options.keyIdFragment - The key identifier fragment. diff --git a/lib/index.js b/lib/index.js index 44ca644..35b0d74 100644 --- a/lib/index.js +++ b/lib/index.js @@ -7,14 +7,14 @@ import {DidKeyDriver} from './DidKeyDriver.js'; /** * Helper method to match the `.driver()` API of other `did-io` plugins. * - * @param {object} options - Options hashmap. - * @param {object} [options.verificationSuite=Ed25519VerificationKey2020] - - * Key suite for the signature verification key suite to use. + * @param {object} options - Options object. + * @param {Map} [options.verificationMethods] - + * A map of verification methods with the key as the publicKeyFormat. * * @returns {DidKeyDriver} Returns an instance of a did:key resolver driver. */ -function driver({verificationSuite} = {}) { - return new DidKeyDriver({verificationSuite}); +function driver({verificationMethods} = {}) { + return new DidKeyDriver({verificationMethods}); } export {driver, DidKeyDriver}; From 3ffb238d409e66f5c99f217545557b913d8c231a Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Sun, 21 Aug 2022 17:53:39 -0400 Subject: [PATCH 38/44] Tighten up parse step to throw if a did key can not be parsed. --- lib/validators.js | 57 ++++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/lib/validators.js b/lib/validators.js index 2b3afe6..2997a84 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -39,25 +39,11 @@ export function validatePublicKeyFormat({publicKeyFormat}) { * @returns {undefined} If the didKeyComponents are valid. */ export function validateDidKey({did}) { - const {scheme, method, version, multibase} = parseDidKey({did}); - if(scheme !== 'did') { - throw new DidResolverError({ - message: `Scheme must be "did" received "${scheme}"`, - code: 'invalidDid' - }); - } - if(method !== 'key') { - throw new DidResolverError({ - message: `Method must be "key" received "${method}"`, - code: 'invalidDid' - }); - } - if(!multibase.startsWith('z')) { - throw new DidResolverError({ - message: `Multibase must start with "z" received ${multibase[0]}`, - code: 'invalidDid' - }); - } + // the parse step will throw if the did doesn't follow the format: + // did:key or the multibase value doesn't start with z + const {version} = parseDidKey({did}); + // so we just need to validate that the version + // is convertible to a positive integer _validateVersion({version}); } @@ -132,12 +118,24 @@ function _validateVersion({version}) { } export function parseDidKey({did}) { + const pchar = '[a-zA-Z0-9\\-\\._~]|%[0-9a-fA-F]{2}|[!$&\'()*+,;=:@]'; + const didKeyPattern = '^(?did):(?key)' + + `(:(?\\d+))?:(?z(${pchar})+)`; + const match = new RegExp(didKeyPattern).exec(did); + if(!match) { + throw new DidResolverError({ + message: `Invalid DID ${did}`, + code: 'invalidDid' + }); + } const { - scheme, - method, - version = '', - multibase - } = _getBaseDidKey({did}); + groups: { + scheme, + method, + version = '', + multibase + } + } = match; return { scheme, method, @@ -146,17 +144,6 @@ export function parseDidKey({did}) { }; } -// if we have a did url we need to remove those components -// so we can correctly parse the identifier after the method -function _getBaseDidKey({did}) { - const pchar = '[a-zA-Z0-9\\-\\._~]|%[0-9a-fA-F]{2}|[!$&\'()*+,;=:@]'; - const didKeyPattern = '(?[a-z]+):(?[a-z0-9]+)' + - `(:(?\\d+))?:(?z(${pchar})+)`; - // this can return null if scheme, method, and multibase are not defined - const {groups = {}} = (new RegExp(didKeyPattern).exec(did) || {}); - return groups; -} - function _validateEncryptionMethod({method, verificationFormats}) { //keyAgreement is an encryption verification method const encryptionFormats = [ From 2153da12739bef96585a0bde008ccb316b56f8c2 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 24 Mar 2023 10:25:53 -0600 Subject: [PATCH 39/44] Update CHANGELOG.md by removing extra line. Co-authored-by: David I. Lehn --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc5d3f3..d283203 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,6 @@ - Added option `enableExperimentalPublicKeyTypes` to `DidKeyDriver` options. - Added option `defaultContext` to `DidKeyDriver` options. - ### Changed - **BREAKING**: `DidKeyDriver` now accepts a Map of `verificationMethods` in the constructor. From c9b2cae701f9756a5fb0d926578578dffabd7cca Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 24 Mar 2023 10:26:30 -0600 Subject: [PATCH 40/44] Update README.md by Capitalizing words and using and over &. Co-authored-by: David I. Lehn --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 01e4571..ae71039 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,8 @@ const didDocument = await didKeyDriver.get({did}); ``` (Results in the [example DID Doc](#example-did-document) above). -### options for `get`, `publicKeyToDidDoc`, & `generate` +### Options for `get`, `publicKeyToDidDoc`, and `generate` + `get`, `publicKeyToDidDoc`, and `generate` both take an options object with the following options: ```js From 3c84f1d59c1518399582b1ee2eabb64a8937c184 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 24 Mar 2023 10:29:29 -0600 Subject: [PATCH 41/44] Update lib/verificationMethods.js Imports with better spacing. Co-authored-by: David I. Lehn --- lib/verificationMethods.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/verificationMethods.js b/lib/verificationMethods.js index 77f89fa..2211ca7 100644 --- a/lib/verificationMethods.js +++ b/lib/verificationMethods.js @@ -2,8 +2,9 @@ * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ -import {Ed25519VerificationKey2018} from - '@digitalbazaar/ed25519-verification-key-2018'; +import { + Ed25519VerificationKey2018 +} from '@digitalbazaar/ed25519-verification-key-2018'; import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020'; From 767cf252aa952aa135c167633d763216f5428817 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 24 Mar 2023 10:38:32 -0600 Subject: [PATCH 42/44] Update README.md Correcting assignment for defaultContext. Co-authored-by: David I. Lehn --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae71039..b7557ee 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ const options = { enableExperimentalPublicKeyTypes: false, // the context for the resulting did document // the default is the did context - defaultContext = [DID_CONTEXT_URL], + defaultContext: [DID_CONTEXT_URL], // if false no keyAgreementKey is included // defaults to true enableEncryptionKeyDerivation = true From 4e7bc2a83b5a55a4e95d7b2bf77f56d04f8e6793 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 24 Mar 2023 10:39:24 -0600 Subject: [PATCH 43/44] Update README.md Correct incorrect assignment for enableEncryptionKeyDerivation. Co-authored-by: David I. Lehn --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b7557ee..4346606 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ const options = { defaultContext: [DID_CONTEXT_URL], // if false no keyAgreementKey is included // defaults to true - enableEncryptionKeyDerivation = true + enableEncryptionKeyDerivation: true }; const did = 'did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T'; From ab06c99ed4e93638b4156ccfa873e9b7e999fc38 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 24 Mar 2023 12:12:17 -0600 Subject: [PATCH 44/44] Add better explanations to options in README. --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4346606..d0b149c 100644 --- a/README.md +++ b/README.md @@ -192,12 +192,13 @@ const didDocument = await didKeyDriver.get({did}); ```js const options = { - // default publicKeyFormat + // default publicKeyFormat for the keys in the didDocument publicKeyFormat: 'Ed25519VerificationKey2020', - // this defaults to false + // enableExperimentalPublicKeyTypes defaults to false. Setting it to true enables + // the use of key types that are not Multikey, JsonWebKey2020, or Ed25519VerificationKey2020. enableExperimentalPublicKeyTypes: false, // the context for the resulting did document - // the default is the did context + // the default is just the did context defaultContext: [DID_CONTEXT_URL], // if false no keyAgreementKey is included // defaults to true