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

Implement a publicMethodFor() helper function. #38

Merged
merged 6 commits into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
50 changes: 44 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ Map(2) {

```

`methodFor` is a convenience function that returns a key pair instance for a
given purpose. For example, a verification key (containing a `signer()` and
`verifier()` functions) are frequently useful for
`methodFor` is a convenience function that returns a public/private key pair
instance for a given purpose. For example, a verification key (containing a
`signer()` and `verifier()` functions) are frequently useful for
[`jsonld-signatures`](https://github.com/digitalbazaar/jsonld-signatures) or
[`vc-js`](https://github.com/digitalbazaar/vc-js) operations. After generating
a new did:key DID, you can do:
Expand All @@ -167,8 +167,15 @@ const invocationKeyPair = methodFor({purpose: 'capabilityInvocation'});
const keyAgreementPair = methodFor({purpose: 'keyAgreement'});
```

Note that `methodFor` returns a key pair that contains both a public and private
key pair (since it has access to the `keyPairs` map from `generate()`).
This makes it useful for _signing_ and _encrypting_ operations (unlike the
`publicMethodFor` that's returned by `get()`, below).

### `get()`

#### Getting a full DID Document from a `did:key` DID

To get a DID Document for an existing `did:key` DID:

```js
Expand All @@ -178,9 +185,11 @@ const didDocument = await didKeyDriver.get({did});

(Results in the [example DID Doc](#example-did-document) above).

You can also use a `.get()` to retrieve an individual key (this is useful
for constructing `documentLoader`s for JSON-LD Signature libs, and the resulting
key does include the appropriate `@context`).
#### 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
already (this is useful for constructing `documentLoader`s for JSON-LD Signature
libs, and the resulting key does include the appropriate `@context`).

```js
const verificationKeyId = 'did:key:z6MkuBLrjSGt1PPADAvuv6rmvj4FfSAfffJotC6K8ZEorYmv#z6MkuBLrjSGt1PPADAvuv6rmvj4FfSAfffJotC6K8ZEorYmv';
Expand All @@ -206,6 +215,35 @@ await didKeyDriver.get({url: keyAgreementKeyId});
}
```

### `publicMethodFor()`

Often, you have just a `did:key` DID, and you need to get a key for a
particular _purpose_ from it, such as an `assertionMethod` key to verify a
VC signature, or a `keyAgreement` key to encrypt a document for that DID's
controller.

For that purpose, you can use a combination of `get()` and `publicMethodFor`:

```js
// Start with the DID
const didDocument = await didKeyDriver.get({did});
// This lets you use `publicMethodFor()` to get a key for a specific purpose
const keyAgreementData = didKeyDriver.publicMethodFor({
didDocument, purpose: 'keyAgreement'
});
const assertionMethodData = didKeyDriver.publicMethodFor({
didDocument, purpose: 'assertionMethod'
});

// If you're using a `crypto-ld` driver harness, you can create key instances
// which allow you to get access to a `verify()` function.
const assertionMethodPublicKey = await cryptoLd.from(assertionMethodData);
const {verify} = assertionMethodPublicKey.verifier();
```

`publicMethodFor` will return `undefined` if no key is found for a given
purpose.

## Contribute

See [the contribute file](https://github.com/digitalbazaar/bedrock/blob/master/CONTRIBUTING.md)!
Expand Down
3 changes: 2 additions & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ module.exports = function(config) {
resolve: {
fallback: {
url: false,
crypto: false
crypto: false,
global: false
}
}
},
Expand Down
42 changes: 40 additions & 2 deletions lib/DidKeyDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from '@digitalbazaar/x25519-key-agreement-key-2020';

import didContext from 'did-context';
import {findVerificationMethod} from '@digitalbazaar/did-io';
import * as didIo from '@digitalbazaar/did-io';
import ed25519Context from 'ed25519-signature-2020-context';
import x25519Context from 'x25519-key-agreement-2020-context';

Expand Down Expand Up @@ -52,7 +52,7 @@ export class DidKeyDriver {
// Convenience function that returns the public/private key pair instance
// for a given purpose (authentication, assertionMethod, keyAgreement, etc).
const methodFor = ({purpose}) => {
const {id: methodId} = findVerificationMethod({
const {id: methodId} = didIo.findVerificationMethod({
doc: didDocument, purpose
});
return keyPairs.get(methodId);
Expand All @@ -61,6 +61,44 @@ export class DidKeyDriver {
return {didDocument, keyPairs, methodFor};
}

/**
* Returns the public key (verification method) object for a given DID
* Document and purpose. Useful in conjunction with a `.get()` call.
*
* @example
* const didDocument = await didKeyDriver.get({did});
* const authKeyData = didDriver.publicMethodFor({
* didDocument, purpose: 'authentication'
* });
* // You can then create a suite instance object to verify signatures etc.
* const authPublicKey = await cryptoLd.from(authKeyData);
* const {verify} = authPublicKey.verifier();
*
* @param {object} options - Options hashmap.
* @param {object} options.didDocument - DID Document (retrieved via a
* `.get()` or from some other source).
* @param {string} options.purpose - Verification method purpose, such as
* 'authentication', 'assertionMethod', 'keyAgreement' and so on.
*
* @returns {object} Returns the public key object (obtained from the DID
* Document), without a `@context`. Returns undefined if no key is found
* for that purpose.
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs updating now.

*/
publicMethodFor({didDocument, purpose} = {}) {
if(!didDocument) {
throw new TypeError('The "didDocument" parameter is required.');
}
if(!purpose) {
mattcollier marked this conversation as resolved.
Show resolved Hide resolved
throw new TypeError('The "purpose" parameter is required.');
}

const method = didIo.findVerificationMethod({doc: didDocument, purpose});
if(!method) {
throw new Error(`No verification method found for purpose "${purpose}"`);
}
return method;
}

/**
* Returns a `did:key` method DID Document for a given DID, or a key document
* for a given DID URL (key id).
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"module": "lib/main.js",
"dependencies": {
"@digitalbazaar/did-io": "^1.0.0",
"@digitalbazaar/ed25519-verification-key-2020": "^2.0.0",
"@digitalbazaar/ed25519-verification-key-2020": "^2.1.1",
"@digitalbazaar/x25519-key-agreement-key-2020": "^1.0.0",
"did-context": "^3.0.0",
"ed25519-signature-2020-context": "^1.0.1",
Expand Down
49 changes: 47 additions & 2 deletions test/driver.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('did:key method driver', () => {
});
});

describe('generate', async () => {
describe('generate', () => {
it('should generate and get round trip', async () => {
const {
didDocument, keyPairs, methodFor
Expand All @@ -98,7 +98,52 @@ describe('did:key method driver', () => {
});
});

describe('computeId', async () => {
describe('publicMethodFor', () => {
mattcollier marked this conversation as resolved.
Show resolved Hide resolved
it('should find a key for a did doc and purpose', async () => {
const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH';
// First, get the did document
const didDocument = await didKeyDriver.get({did});
// Then publicMethodFor can be used to fetch key data
const keyAgreementData = didKeyDriver.publicMethodFor({
didDocument, purpose: 'keyAgreement'
});
expect(keyAgreementData).to.have
.property('type', 'X25519KeyAgreementKey2020');
expect(keyAgreementData).to.have
.property('publicKeyMultibase',
'zJhNWeSVLMYccCk7iopQW4guaSJTojqpMEELgSLhKwRr');

const authKeyData = didKeyDriver.publicMethodFor({
didDocument, purpose: 'authentication'
});
expect(authKeyData).to.have
.property('type', 'Ed25519VerificationKey2020');
expect(authKeyData).to.have
.property('publicKeyMultibase',
'zB12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u');
});

it('should throw error if key is not found for purpose', async () => {
const did = 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH';
// First, get the did document
const didDocument = await didKeyDriver.get({did});

let error;
try {
didKeyDriver.publicMethodFor({
didDocument, purpose: 'invalidPurpose'
});
} catch(e) {
error = e;
}

expect(error).to.be.exist;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
expect(error).to.be.exist;
expect(error).to.exist;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Whoops.

expect(error.message).to
.match(/No verification method found for purpose/);
Copy link
Contributor

Choose a reason for hiding this comment

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

can avoid regex with .to.contain()

});
});

describe('computeId', () => {
const keyPair = {fingerprint: () => '12345'};

it('should set the key id based on fingerprint', async () => {
Expand Down