Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add did key spec 7 validators #47

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
a9d5787
Use eslint-config-db 4.1.
aljones15 Aug 4, 2022
de54f3c
Add DidKeyError.
aljones15 Aug 4, 2022
71c631b
Start validator.
aljones15 Aug 4, 2022
890472b
Sort imports alphabetically.
aljones15 Aug 4, 2022
8067182
Port validators over.
aljones15 Aug 5, 2022
d8e7b3c
Use ed25519 verification key with did key error causes.
aljones15 Aug 5, 2022
018cbd7
Use DidResolutionError from did-io.
aljones15 Aug 5, 2022
64d7741
Add parseDidKey to validators & use it.
aljones15 Aug 5, 2022
3621bf9
Use main for ed25519-verification-key-2020.
aljones15 Aug 8, 2022
971ee7c
Assert on did key scheme.
aljones15 Aug 9, 2022
9f6089f
Start Major release changelog for did:key v4.
aljones15 Aug 9, 2022
46b2058
Start work on validating did document creation options.
aljones15 Aug 9, 2022
6f4233a
Add didDocument validator.
aljones15 Aug 9, 2022
cfd3765
Use enableExperimentalPublicKeyTypes: true for 2018 ed key.
aljones15 Aug 10, 2022
0f58597
Use @digitalbazaar/ed25519-verification-key-2020: ^4.1.0
aljones15 Aug 10, 2022
8d69300
Use publicKeyFormat to get verificationMethod.
aljones15 Aug 10, 2022
79fa10a
Move vm related logic to ./lib/verificationMethods.js.
aljones15 Aug 10, 2022
2a8affe
Remove didKeyDriver2018 from tests & add noKaK test.
aljones15 Aug 10, 2022
add28ac
Lint and disable linter in expected didDoc file.
aljones15 Aug 10, 2022
dc6089d
Move ed25519 verification key 2018 to deps.
aljones15 Aug 10, 2022
23173d5
Use better naming convention for map of verification methods.
aljones15 Aug 10, 2022
39ced6b
Add check for representationNotSupported.
aljones15 Aug 10, 2022
75c2b16
Clarify rationale behind representationNotSupported & unknownPublicKe…
aljones15 Aug 10, 2022
3c969b5
Update CHANGELOG for 4.0 release.
aljones15 Aug 12, 2022
e03957f
Add --full-trace & --check-leaks to test options.
aljones15 Aug 12, 2022
e94064a
Add tests for Multikey, JsonWebKey2020, & experimental types false.
aljones15 Aug 12, 2022
7bb57e8
Add tests for scheme, method, and multibase.
aljones15 Aug 12, 2022
4f14324
Add tests for version number in did:key.
aljones15 Aug 12, 2022
56f551a
Test corrections.
aljones15 Aug 12, 2022
877883b
Add test for unsupportedPublicKeyType.
aljones15 Aug 12, 2022
048e695
Correct test title.
aljones15 Aug 12, 2022
ad51d3d
Drop node 14 from ci actions.
aljones15 Aug 12, 2022
49e302a
Add README section for Ed25519VerificationKey2018.
aljones15 Aug 17, 2022
df187bc
Add README docs for options.
aljones15 Aug 18, 2022
5696f2e
Add example of using options with get.
aljones15 Aug 18, 2022
9904378
Set all defaults in one place & improve jsdoc.
aljones15 Aug 18, 2022
2d7fdf3
Replace hashmap w/ object in comments & fix driver creation function.
aljones15 Aug 18, 2022
3ffb238
Tighten up parse step to throw if a did key can not be parsed.
aljones15 Aug 21, 2022
2153da1
Update CHANGELOG.md by removing extra line.
aljones15 Mar 24, 2023
c9b2cae
Update README.md by Capitalizing words and using and over &.
aljones15 Mar 24, 2023
3c84f1d
Update lib/verificationMethods.js Imports with better spacing.
aljones15 Mar 24, 2023
767cf25
Update README.md Correcting assignment for defaultContext.
aljones15 Mar 24, 2023
4e7bc2a
Update README.md Correct incorrect assignment for enableEncryptionKey…
aljones15 Mar 24, 2023
ab06c99
Add better explanations to options in README.
aljones15 Mar 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# did:key driver ChangeLog

## 4.0.0 -

### Added
- **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.
- Added option `defaultContext` to `DidKeyDriver` options.

aljones15 marked this conversation as resolved.
Show resolved Hide resolved
### 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

### Changed
Expand Down
38 changes: 30 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,31 @@ 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`, and `generate`

`get`, `publicKeyToDidDoc`, and `generate` both take an options object with the following options:

```js
const options = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have better docs here. Unclear what some of these do beyond what the names imply. Some of the comments are redundant ("defaults to false", then it's set to false).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more expansive documentation for the first 2 options: ab06c99

// default publicKeyFormat for the keys in the didDocument
publicKeyFormat: 'Ed25519VerificationKey2020',
// 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 just the did context
defaultContext: [DID_CONTEXT_URL],
// if false no keyAgreementKey is included
// 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

You can also use a `.get()` to retrieve an individual key, if you know it's id
Expand Down Expand Up @@ -254,17 +276,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 defaults to false
enableExperimentalPublicKeyTypes: true
};
await didKeyDriver.get({did, options});
// ->
{
'@context': [
Expand Down
165 changes: 91 additions & 74 deletions lib/DidKeyDriver.js
Original file line number Diff line number Diff line change
@@ -1,64 +1,59 @@
/*!
* Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved.
*/
import {
Ed25519VerificationKey2020
} from '@digitalbazaar/ed25519-verification-key-2020';
import {
X25519KeyAgreementKey2020
} from '@digitalbazaar/x25519-key-agreement-key-2020';
import {
X25519KeyAgreementKey2019
} from '@digitalbazaar/x25519-key-agreement-key-2019';

import * as didIo from '@digitalbazaar/did-io';

import {
parseDidKey,
validateDidDocument,
validateDidKey,
validatePublicKeyFormat
} from './validators.js';
import {DidResolverError} from '@digitalbazaar/did-io';
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]
]);
import {
contextsBySuite,
getEncryptionMethod,
methodsByKeyFormat
} from './verificationMethods.js';

export class DidKeyDriver {
/**
* @param {object} options - Options hashmap.
* @param {object} [options.verificationSuite=Ed25519VerificationKey2020] -
* Key suite for the signature verification key suite to use.
* @param {object} options - Options to use.
* @param {Map<string, object>} [options.verificationMethods] -
* A map of verification methods with the key as the publicKeyFormat.
*/
constructor({verificationSuite = Ed25519VerificationKey2020} = {}) {
constructor({
verificationMethods = methodsByKeyFormat,
} = {}) {
// used by did-io to register drivers
this.method = 'key';
this.verificationSuite = verificationSuite;
this.verificationMethods = verificationMethods;
}

/**
* 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.
*
* @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});

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.
const {didDocument, keyPairs} = await this._keyPairToDidDocument({
keyPair: verificationKeyPair
keyPair: verificationKeyPair,
options
});

// Convenience function that returns the public/private key pair instance
Expand Down Expand Up @@ -86,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
Expand Down Expand Up @@ -119,27 +114,29 @@ 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
* for better readability of invoking code.
* @param {object} [options.options] - Options for didDocument creation.
*
* @returns {Promise<object>} 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.');
}
validateDidKey({did});

const [didAuthority, keyIdFragment] = did.split('#');
const {multibase: fingerprint} = parseDidKey({did: didAuthority});
const keyPair = this._getVerificationMethod(options).
fromFingerprint({fingerprint});

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
Expand All @@ -155,67 +152,55 @@ 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
* 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<object>} 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};
}

/**
* 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)).
* @param {object} [options.options = {}] - 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} = {}) {
const verificationKeyPair = await this.verificationSuite.from({...keyPair});
async _keyPairToDidDocument({keyPair, options = {}} = {}) {
const {
publicKeyFormat = 'Ed25519VerificationKey2020',
enableExperimentalPublicKeyTypes = false,
defaultContext = [DID_CONTEXT_URL],
enableEncryptionKeyDerivation = true
} = options;
const verificationKeyPair = await this._getVerificationMethod(
{publicKeyFormat}).from({...keyPair});
const did = `did:key:${verificationKeyPair.fingerprint()}`;
verificationKeyPair.controller = did;

const contexts = [DID_CONTEXT_URL];

// 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 contexts = [...defaultContext];
// 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 = {
Expand All @@ -227,22 +212,54 @@ export class DidKeyDriver {
authentication: [publicEdKey.id],
assertionMethod: [publicEdKey.id],
capabilityDelegation: [publicEdKey.id],
capabilityInvocation: [publicEdKey.id],
keyAgreement: [publicDhKey]
capabilityInvocation: [publicEdKey.id]
};

// create the key pairs map
const keyPairs = new Map();
keyPairs.set(verificationKeyPair.id, verificationKeyPair);
keyPairs.set(keyAgreementKeyPair.id, keyAgreementKeyPair);

// 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];
}
validateDidDocument({
didDocument,
didOptions: {
enableExperimentalPublicKeyTypes,
enableEncryptionKeyDerivation
}
});

return {didDocument, keyPairs};
}

_getVerificationMethod({
publicKeyFormat = 'Ed25519VerificationKey2020'
} = {}) {
// 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 then the method is not supported by
// the `did:key` spec
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.
*
* @param {object} options - Options hashmap.
* @param {object} options - Options object.
* @param {LDKeyPair} options.keyPair - The key pair used when computing the
* identifier.
*
Expand All @@ -256,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.
Expand Down
10 changes: 5 additions & 5 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, object>} [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};
Loading