diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f65e77..09723f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # @digitalbazaar/ed25519-verification-key-2020 ChangeLog +## 4.2.0 - 2024-10-dd + +### Added +- Add support for reading `JsonWebKey`-typed keys. + ## 4.1.0 - 2022-08-09 ### Added diff --git a/lib/Ed25519VerificationKey2020.js b/lib/Ed25519VerificationKey2020.js index 4339c0c..f8552f9 100644 --- a/lib/Ed25519VerificationKey2020.js +++ b/lib/Ed25519VerificationKey2020.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. */ import * as base58btc from 'base58-universal'; import * as base64url from 'base64url-universal'; @@ -92,8 +92,8 @@ export class Ed25519VerificationKey2020 extends LDKeyPair { if(options.type === 'Ed25519VerificationKey2018') { return Ed25519VerificationKey2020.fromEd25519VerificationKey2018(options); } - if(options.type === 'JsonWebKey2020') { - return Ed25519VerificationKey2020.fromJsonWebKey2020(options); + if(options.type === 'JsonWebKey' || options.type === 'JsonWebKey2020') { + return Ed25519VerificationKey2020.fromJsonWebKey(options); } return new Ed25519VerificationKey2020(options); } @@ -144,6 +144,26 @@ export class Ed25519VerificationKey2020 extends LDKeyPair { if(type !== 'JsonWebKey2020') { throw new TypeError(`Invalid key type: "${type}".`); } + return Ed25519VerificationKey2020.fromJsonWebKey({ + id, type, controller, publicKeyJwk + }); + } + + /** + * Creates a key pair instance (public key only) from a JsonWebKey object. + * + * @param {object} options - Options hashmap. + * @param {string} options.id - Key id. + * @param {string} options.type - Key suite type. + * @param {string} options.controller - Key controller. + * @param {object} options.publicKeyJwk - JWK object. + * + * @returns {Promise} Resolves with key pair. + */ + static fromJsonWebKey({id, type, controller, publicKeyJwk} = {}) { + if(!(type === 'JsonWebKey' || type === 'JsonWebKey2020')) { + throw new TypeError(`Invalid key type: "${type}".`); + } if(!publicKeyJwk) { throw new TypeError('"publicKeyJwk" property is required.'); } @@ -331,6 +351,21 @@ export class Ed25519VerificationKey2020 extends LDKeyPair { new Uint8Array(await ed25519.sha256digest({data}))); } + /** + * Returns the JsonWebKey representation of this key pair. + * + * @returns {Promise} JsonWebKey representation. + */ + async toJsonWebKey() { + return { + '@context': 'https://w3id.org/security/jwk/v1', + id: this.controller + '#' + await this.jwkThumbprint(), + type: 'JsonWebKey', + controller: this.controller, + publicKeyJwk: this.toJwk({publicKey: true}) + }; + } + /** * Returns the JsonWebKey2020 representation of this key pair. * diff --git a/test/Ed25519VerificationKey2020.spec.js b/test/Ed25519VerificationKey2020.spec.js index a6d7ee2..0847c36 100644 --- a/test/Ed25519VerificationKey2020.spec.js +++ b/test/Ed25519VerificationKey2020.spec.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2020-2022 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2020-2024 Digital Bazaar, Inc. All rights reserved. */ import chai from 'chai'; import * as base58btc from 'base58-universal'; @@ -304,6 +304,52 @@ describe('Ed25519VerificationKey2020', () => { }); }); + describe('JsonWebKey', () => { + it('round trip imports/exports', async () => { + const keyData = { + '@context': 'https://w3id.org/security/jwk/v1', + id: 'did:example:123#kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k', + type: 'JsonWebKey', + controller: 'did:example:123', + publicKeyJwk: { + kty: 'OKP', + crv: 'Ed25519', + x: '11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo' + } + }; + + const key = await Ed25519VerificationKey2020.from(keyData); + + expect(key.controller).to.equal('did:example:123'); + expect(key.id).to + .equal('did:example:123#kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k'); + expect(key.publicKeyMultibase).to + .equal('z6MktwupdmLXVVqTzCw4i46r4uGyosGXRnR3XjN4Zq7oMMsw'); + + const exported = await key.toJsonWebKey(); + + expect(exported).to.eql(keyData); + }); + + it('computes jwk thumbprint', async () => { + const keyData = { + id: 'did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A', + type: 'JsonWebKey', + controller: 'did:example:123', + publicKeyJwk: { + kty: 'OKP', + crv: 'Ed25519', + x: 'VCpo2LMLhn6iWku8MKvSLg2ZAoC-nlOyPVQaO3FxVeQ' + } + }; + + const key = await Ed25519VerificationKey2020.from(keyData); + + expect(await key.jwkThumbprint()).to + .equal('_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A'); + }); + }); + describe('JsonWebKey2020', () => { it('round trip imports/exports', async () => { const keyData = {