From 5ad0bfb713dca8fd24b99ddf053335340a39e7b3 Mon Sep 17 00:00:00 2001 From: dchagastelles Date: Wed, 30 Nov 2022 13:27:58 -0500 Subject: [PATCH] feat(did-provider-pkh): implement did:pkh support. (#1052) fixes #1024 --- .gitignore | 2 + __tests__/localAgent.test.ts | 5 + __tests__/localJsonStoreAgent.test.ts | 5 + __tests__/localMemoryStoreAgent.test.ts | 5 + __tests__/restAgent.test.ts | 5 + __tests__/shared/didManager.ts | 13 ++ __tests__/shared/resolveDid.ts | 17 ++ __tests__/shared/verifiableDataLD.ts | 32 +++ package.json | 1 + packages/credential-ld/src/action-handler.ts | 1 + packages/did-provider-pkh/LICENSE | 201 ++++++++++++++++++ packages/did-provider-pkh/README.md | 4 + packages/did-provider-pkh/api-extractor.json | 18 ++ packages/did-provider-pkh/package.json | 37 ++++ packages/did-provider-pkh/src/index.ts | 8 + .../did-provider-pkh/src/pkh-did-provider.ts | 136 ++++++++++++ packages/did-provider-pkh/src/resolver.ts | 93 ++++++++ packages/did-provider-pkh/tsconfig.json | 10 + packages/test-react-app/src/veramo/setup.ts | 5 + packages/tsconfig.json | 1 + scripts/prepare-react-test.sh | 1 + yarn.lock | 14 ++ 22 files changed, 614 insertions(+) create mode 100644 packages/did-provider-pkh/LICENSE create mode 100644 packages/did-provider-pkh/README.md create mode 100644 packages/did-provider-pkh/api-extractor.json create mode 100644 packages/did-provider-pkh/package.json create mode 100644 packages/did-provider-pkh/src/index.ts create mode 100644 packages/did-provider-pkh/src/pkh-did-provider.ts create mode 100644 packages/did-provider-pkh/src/resolver.ts create mode 100644 packages/did-provider-pkh/tsconfig.json diff --git a/.gitignore b/.gitignore index 367d1cf99..496fa1d75 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ temp agent.yml data .vscode-upload.json + +local-database*.json \ No newline at end of file diff --git a/__tests__/localAgent.test.ts b/__tests__/localAgent.test.ts index 39503df2e..5c02e81d9 100644 --- a/__tests__/localAgent.test.ts +++ b/__tests__/localAgent.test.ts @@ -36,6 +36,7 @@ import { import { EthrDIDProvider } from '../packages/did-provider-ethr/src' import { WebDIDProvider } from '../packages/did-provider-web/src' import { getDidKeyResolver, KeyDIDProvider } from '../packages/did-provider-key/src' +import { getDidPkhResolver, PkhDIDProvider } from '../packages/did-provider-pkh/src' import { DIDComm, DIDCommHttpTransport, DIDCommMessageHandler, IDIDComm } from '../packages/did-comm/src' import { ISelectiveDisclosure, @@ -193,6 +194,9 @@ const setup = async (options?: IAgentOptions): Promise => { 'did:key': new KeyDIDProvider({ defaultKms: 'local', }), + 'did:pkh': new PkhDIDProvider({ + defaultKms: 'local', + }), 'did:fake': new FakeDidProvider(), }, }), @@ -210,6 +214,7 @@ const setup = async (options?: IAgentOptions): Promise => { }), ...webDidResolver(), ...getDidKeyResolver(), + ...getDidPkhResolver(), ...new FakeDidResolver(() => agent).getDidFakeResolver(), }), new DataStore(dbConnection), diff --git a/__tests__/localJsonStoreAgent.test.ts b/__tests__/localJsonStoreAgent.test.ts index 03d63d398..f6c979ee3 100644 --- a/__tests__/localJsonStoreAgent.test.ts +++ b/__tests__/localJsonStoreAgent.test.ts @@ -34,6 +34,7 @@ import { import { EthrDIDProvider } from '../packages/did-provider-ethr/src' import { WebDIDProvider } from '../packages/did-provider-web/src' import { getDidKeyResolver, KeyDIDProvider } from '../packages/did-provider-key/src' +import { getDidPkhResolver, PkhDIDProvider } from '../packages/did-provider-pkh/src' import { DIDComm, DIDCommMessageHandler, IDIDComm } from '../packages/did-comm/src' import { ISelectiveDisclosure, @@ -157,6 +158,9 @@ const setup = async (options?: IAgentOptions): Promise => { 'did:key': new KeyDIDProvider({ defaultKms: 'local', }), + 'did:pkh': new PkhDIDProvider({ + defaultKms: 'local', + }), 'did:fake': new FakeDidProvider(), }, }), @@ -165,6 +169,7 @@ const setup = async (options?: IAgentOptions): Promise => { ...ethrDidResolver({ infuraProjectId }), ...webDidResolver(), ...getDidKeyResolver(), + ...getDidPkhResolver(), ...new FakeDidResolver(() => agent).getDidFakeResolver(), }), }), diff --git a/__tests__/localMemoryStoreAgent.test.ts b/__tests__/localMemoryStoreAgent.test.ts index 9a7bdf075..6d4a45149 100644 --- a/__tests__/localMemoryStoreAgent.test.ts +++ b/__tests__/localMemoryStoreAgent.test.ts @@ -35,6 +35,7 @@ import { import { EthrDIDProvider } from '../packages/did-provider-ethr/src' import { WebDIDProvider } from '../packages/did-provider-web/src' import { getDidKeyResolver, KeyDIDProvider } from '../packages/did-provider-key/src' +import { getDidPkhResolver, PkhDIDProvider } from '../packages/did-provider-pkh/src' import { DIDComm, DIDCommMessageHandler, IDIDComm } from '../packages/did-comm/src' import { ISelectiveDisclosure, @@ -155,6 +156,9 @@ const setup = async (options?: IAgentOptions): Promise => { 'did:key': new KeyDIDProvider({ defaultKms: 'local', }), + 'did:pkh': new PkhDIDProvider({ + defaultKms: 'local', + }), 'did:fake': new FakeDidProvider(), }, }), @@ -163,6 +167,7 @@ const setup = async (options?: IAgentOptions): Promise => { ...ethrDidResolver({ infuraProjectId }), ...webDidResolver(), ...getDidKeyResolver(), + ...getDidPkhResolver(), ...new FakeDidResolver(() => agent).getDidFakeResolver(), }), }), diff --git a/__tests__/restAgent.test.ts b/__tests__/restAgent.test.ts index 3ea1f5b45..f7a601242 100644 --- a/__tests__/restAgent.test.ts +++ b/__tests__/restAgent.test.ts @@ -44,6 +44,7 @@ import { import { EthrDIDProvider } from '../packages/did-provider-ethr/src' import { WebDIDProvider } from '../packages/did-provider-web/src' import { getDidKeyResolver, KeyDIDProvider } from '../packages/did-provider-key/src' +import { getDidPkhResolver, PkhDIDProvider } from '../packages/did-provider-pkh/src' import { DIDComm, DIDCommHttpTransport, DIDCommMessageHandler, IDIDComm } from '../packages/did-comm/src' import { ISelectiveDisclosure, @@ -184,6 +185,9 @@ const setup = async (options?: IAgentOptions): Promise => { 'did:key': new KeyDIDProvider({ defaultKms: 'local', }), + 'did:pkh': new PkhDIDProvider({ + defaultKms: 'local', + }), 'did:fake': new FakeDidProvider(), }, }), @@ -193,6 +197,7 @@ const setup = async (options?: IAgentOptions): Promise => { ...webDidResolver(), // key: getUniversalResolver(), // resolve using remote resolver... when uniresolver becomes more stable, ...getDidKeyResolver(), + ...getDidPkhResolver(), ...new FakeDidResolver(() => serverAgent as TAgent).getDidFakeResolver(), }), }), diff --git a/__tests__/shared/didManager.ts b/__tests__/shared/didManager.ts index 7398ab1b6..e1b2acb1a 100644 --- a/__tests__/shared/didManager.ts +++ b/__tests__/shared/didManager.ts @@ -33,6 +33,19 @@ export default (testContext: { expect(identifier.controllerKeyId).toEqual(identifier.keys[0].kid) }) + it('should create pkh identifier using did:pkh provider', async () => { + identifier = await agent.didManagerCreate({ + // this expects the `did:ethr` provider to matchPrefix and use the `arbitrum:goerli` network specifier + provider: 'did:pkh', + options: { chainId: "1"} + }) + expect(identifier.provider).toEqual('did:pkh') + //expect(identifier.did).toMatch(/^did:pkh:eip155:*$/) + expect(identifier.keys.length).toEqual(1) + expect(identifier.services.length).toEqual(0) + expect(identifier.controllerKeyId).toEqual(identifier.keys[0].kid) + }) + it('should create identifier using did:ethr:arbitrum:goerli provider', async () => { identifier = await agent.didManagerCreate({ // this expects the `did:ethr` provider to matchPrefix and use the `arbitrum:goerli` network specifier diff --git a/__tests__/shared/resolveDid.ts b/__tests__/shared/resolveDid.ts index d25fa34ef..514e0c56f 100644 --- a/__tests__/shared/resolveDid.ts +++ b/__tests__/shared/resolveDid.ts @@ -1,5 +1,6 @@ // noinspection ES6PreferShortImport +import { IIdentifier } from '@veramo/core' import { IAgentOptions, IDIDManager, IResolver, TAgent } from '../../packages/core/src' type ConfiguredAgent = TAgent @@ -34,6 +35,22 @@ export default (testContext: { expect(result).toHaveProperty('didResolutionMetadata') }) + it('should resolve did:pkh', async () => { + let identifier: IIdentifier = await agent.didManagerCreate({ + // this expects the `did:ethr` provider to matchPrefix and use the `arbitrum:goerli` network specifier + provider: 'did:pkh', + options: { chainId: "1"} + }); + + const result = await agent.resolveDid({ didUrl: identifier.did}); + const didDoc = result.didDocument + expect(didDoc?.id).toEqual(identifier.did) + expect(result).toHaveProperty('didDocumentMetadata') + expect(result).toHaveProperty('didResolutionMetadata') + + //let cred = await agent.createVerifiableCredential() + }); + it('should resolve imported fake did', async () => { const did = 'did:fake:myfakedid' await agent.didManagerImport({ diff --git a/__tests__/shared/verifiableDataLD.ts b/__tests__/shared/verifiableDataLD.ts index 5c457925e..dbc0ca06d 100644 --- a/__tests__/shared/verifiableDataLD.ts +++ b/__tests__/shared/verifiableDataLD.ts @@ -26,7 +26,9 @@ export default (testContext: { let agent: ConfiguredAgent let didEthrIdentifier: IIdentifier let didKeyIdentifier: IIdentifier + let pkhIdentifier: IIdentifier let storedCredentialHash: string + let storedPkhCredentialHash: string let challenge: string beforeAll(async () => { @@ -35,6 +37,7 @@ export default (testContext: { challenge = 'TEST_CHALLENGE_STRING' didEthrIdentifier = await agent.didManagerCreate({ kms: 'local', provider: 'did:ethr' }) didKeyIdentifier = await agent.didManagerCreate({ kms: 'local', provider: 'did:key' }) + pkhIdentifier = await agent.didManagerCreate({ kms: 'local', provider: "did:pkh", options: { chainId: "1"} }) }) afterAll(testContext.tearDown) @@ -257,6 +260,35 @@ export default (testContext: { expect(result.verified).toEqual(true) }) + it('should create verifiable credential in LD with did:pkh', async () => { + const verifiableCredential = await agent.createVerifiableCredential({ + credential: { + issuer: { id: pkhIdentifier.did }, + '@context': ['https://www.w3.org/2018/credentials/v1', 'https://veramo.io/contexts/profile/v1'], + type: ['VerifiableCredential', 'Profile'], + issuanceDate: new Date().toISOString(), + credentialSubject: { + id: pkhIdentifier.did, + name: 'Martin, the great', + }, + }, + proofFormat: 'lds', + }) + + // Check credential: + expect(verifiableCredential).toHaveProperty('proof') + expect(verifiableCredential).toHaveProperty('proof.jws') + expect(verifiableCredential['type']).toEqual(['VerifiableCredential', 'Profile']) + + storedPkhCredentialHash = await agent.dataStoreSaveVerifiableCredential({ verifiableCredential }) + expect(typeof storedPkhCredentialHash).toEqual('string') + + const verifiableCredential2 = await agent.dataStoreGetVerifiableCredential({ + hash: storedPkhCredentialHash, + }) + expect(verifiableCredential).toEqual(verifiableCredential2) + }) + describe('credential verification policies', () => { it('can verify credential at a particular time', async () => { const issuanceDate = '2019-08-19T09:15:20.000Z' // 1566206120 diff --git a/package.json b/package.json index 992ddc955..a9fc1e416 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "rimraf": "3.0.2", "semantic-release": "19.0.3", "ts-jest": "28.0.4", + "caip": "^1.1.0", "ts-json-schema-generator": "1.1.2", "ts-node": "10.9.1", "typescript": "4.9.3" diff --git a/packages/credential-ld/src/action-handler.ts b/packages/credential-ld/src/action-handler.ts index c3f8549ae..31b7d9dd4 100644 --- a/packages/credential-ld/src/action-handler.ts +++ b/packages/credential-ld/src/action-handler.ts @@ -251,6 +251,7 @@ export class CredentialIssuerLD implements IAgentPlugin { signingKey = extendedKeys.find((k) => supportedTypes.includes(k.meta.verificationMethod.type)) } + if (!signingKey) throw Error(`key_not_found: No suitable signing key found for ${identifier.did}`) verificationMethodId = signingKey.meta.verificationMethod.id return { signingKey, verificationMethodId } diff --git a/packages/did-provider-pkh/LICENSE b/packages/did-provider-pkh/LICENSE new file mode 100644 index 000000000..fd815d7f8 --- /dev/null +++ b/packages/did-provider-pkh/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Consensys AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/did-provider-pkh/README.md b/packages/did-provider-pkh/README.md new file mode 100644 index 000000000..34de2dbc9 --- /dev/null +++ b/packages/did-provider-pkh/README.md @@ -0,0 +1,4 @@ +# Veramo did:web provider + +This package contains an implementation of `AbstractIdentifierProvider` for the `did:pkh` method. +This enables creation and control of `did:pkh` entities. diff --git a/packages/did-provider-pkh/api-extractor.json b/packages/did-provider-pkh/api-extractor.json new file mode 100644 index 000000000..409d7f16c --- /dev/null +++ b/packages/did-provider-pkh/api-extractor.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "apiReport": { + "enabled": true, + "reportFolder": "./api", + "reportTempFolder": "./api" + }, + + "docModel": { + "enabled": true, + "apiJsonFilePath": "./api/.api.json" + }, + + "dtsRollup": { + "enabled": false + }, + "mainEntryPointFilePath": "/build/index.d.ts" +} diff --git a/packages/did-provider-pkh/package.json b/packages/did-provider-pkh/package.json new file mode 100644 index 000000000..5c5761a42 --- /dev/null +++ b/packages/did-provider-pkh/package.json @@ -0,0 +1,37 @@ +{ + "name": "@veramo/did-provider-pkh", + "description": "Veramo plugin that can enable creation and control of did:pkh identifiers.", + "version": "4.0.0", + "main": "build/index.js", + "types": "build/index.d.ts", + "scripts": { + "build": "tsc", + "extract-api": "yarn veramo dev extract-api" + }, + "dependencies": { + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@veramo/core": "^4.0.0", + "@veramo/did-manager": "^4.0.0", + "debug": "^4.3.3" + }, + "devDependencies": { + "@types/debug": "4.1.7", + "typescript": "4.8.4" + }, + "files": [ + "build/**/*", + "src/**/*", + "README.md", + "LICENSE" + ], + "publishConfig": { + "access": "public" + }, + "repository": "git@github.com:uport-project/veramo.git", + "author": "Diego Chagastelles ", + "contributors": [], + "license": "Apache-2.0", + "keywords": [] +} diff --git a/packages/did-provider-pkh/src/index.ts b/packages/did-provider-pkh/src/index.ts new file mode 100644 index 000000000..c7cbbd8f1 --- /dev/null +++ b/packages/did-provider-pkh/src/index.ts @@ -0,0 +1,8 @@ +/** + * Provides `did:pkh` {@link @veramo/did-provider-pkh#PkhDIDProvider | identifier provider } for the + * {@link @veramo/did-manager#DIDManager} + * + * @packageDocumentation + */ +export { PkhDIDProvider } from './pkh-did-provider' +export { getResolver as getDidPkhResolver } from './resolver' diff --git a/packages/did-provider-pkh/src/pkh-did-provider.ts b/packages/did-provider-pkh/src/pkh-did-provider.ts new file mode 100644 index 000000000..6deda5345 --- /dev/null +++ b/packages/did-provider-pkh/src/pkh-did-provider.ts @@ -0,0 +1,136 @@ +import { IIdentifier, IKey, IService, IAgentContext, IKeyManager } from '@veramo/core' +import { Provider } from '@ethersproject/abstract-provider' +import { computeAddress } from '@ethersproject/transactions' + +import { AbstractIdentifierProvider } from '@veramo/did-manager' +import { computePublicKey } from '@ethersproject/signing-key' +import { BigNumber } from '@ethersproject/bignumber' + +import Debug from 'debug' +const debug = Debug('veramo:did-pkh:identifier-provider') + +type IContext = IAgentContext + +/** + * Options for creating a did:ethr + * @beta + */ + export interface CreateDidPkhEthrOptions { + /** + * This can be hex encoded chain ID (string) or a chainId number + * + * If this is not specified, `1` is assumed. + */ + chainId?: string | number +} + + /** + * Helper method that can computes the ethereumAddress corresponding to a Secp256k1 public key. + * @param hexPublicKey A hex encoded public key, optionally prefixed with `0x` + */ + export function toEthereumAddress(hexPublicKey: string): string { + const publicKey = hexPublicKey.startsWith('0x') ? hexPublicKey : '0x' + hexPublicKey + return computeAddress(publicKey) + } + + +/** + * {@link @veramo/did-manager#DIDManager} identifier provider for `did:pkh` identifiers + * + * @beta This API may change without a BREAKING CHANGE notice. + */ +export class PkhDIDProvider extends AbstractIdentifierProvider { + private defaultKms: string + + constructor(options: { + defaultKms: string + }) + { + super() + this.defaultKms = options.defaultKms + } + + + + async createIdentifier( + { kms, options }: { kms?: string; options?: CreateDidPkhEthrOptions }, + context: IContext, + ): Promise> { + + const key = await context.agent.keyManagerCreate({ kms: kms || this.defaultKms, type: 'Secp256k1' }) + const publicAddress = toEthereumAddress(key.publicKeyHex); + + const network = options?.chainId; + if (!network) { + throw new Error( + `invalid_setup: Cannot create did:pkh. There is no known configuration for network=${network}'`, + ) + } + + const identifier: Omit = { + did: 'did:pkh:eip155:' + network + ':' + publicAddress, + controllerKeyId: key.kid, + keys: [key], + services: [], + } + debug('Created', identifier.did) + return identifier + } + async updateIdentifier(args: { did: string; kms?: string | undefined; alias?: string | undefined; options?: any }, context: IAgentContext): Promise { + throw new Error('PkhDIDProvider updateIdentifier not supported yet.') + } + + async deleteIdentifier(identifier: IIdentifier, context: IContext): Promise { + for (const { kid } of identifier.keys) { + await context.agent.keyManagerDelete({ kid }) + } + return true + } + + async addKey( + { identifier, key, options }: { identifier: IIdentifier; key: IKey; options?: any }, + context: IContext, + ): Promise { + throw Error('PkhDIDProvider addKey not supported') + } + + async addService( + { identifier, service, options }: { identifier: IIdentifier; service: IService; options?: any }, + context: IContext, + ): Promise { + throw Error('PkhDIDProvider addService not supported') + } + + async removeKey( + args: { identifier: IIdentifier; kid: string; options?: any }, + context: IContext, + ): Promise { + throw Error('PkhDIDProvider removeKey not supported') + } + + async removeService( + args: { identifier: IIdentifier; id: string; options?: any }, + context: IContext, + ): Promise { + throw Error('PkhDIDProvider removeService not supported') + } + + // private getNetworkFor(networkSpecifier: string | number | undefined): EthrNetworkConfiguration | undefined { + // let networkNameOrId: string | number = networkSpecifier || 'mainnet' + // if ( + // typeof networkNameOrId === 'string' && + // (networkNameOrId.startsWith('0x') || parseInt(networkNameOrId) > 0) + // ) { + // networkNameOrId = BigNumber.from(networkNameOrId).toNumber() + // } + // let network = this.networks?.find( + // (n) => n.chainId === networkNameOrId || n.name === networkNameOrId || n.description === networkNameOrId, + // ) + // if (!network && !networkSpecifier && this.networks?.length === 1) { + // network = this.networks[0] + // } + // return network + // } + + +} diff --git a/packages/did-provider-pkh/src/resolver.ts b/packages/did-provider-pkh/src/resolver.ts new file mode 100644 index 000000000..ab0508988 --- /dev/null +++ b/packages/did-provider-pkh/src/resolver.ts @@ -0,0 +1,93 @@ +import { AccountId, ChainIdParams } from 'caip' +import type { + DIDResolutionResult, + DIDResolutionOptions, + ResolverRegistry, + ParsedDID, + Resolver, + Resolvable +} from 'did-resolver' + +const DID_LD_JSON = 'application/did+ld+json' +const DID_JSON = 'application/did+json' +const SECPK1_NAMESPACES = ['eip155', 'bip122'] +const TZ_NAMESPACE = 'tezos' + + +function toDidDoc(did: string, accountId: string): any { + const { namespace } = AccountId.parse(accountId).chainId as ChainIdParams + const vmId = did + '#blockchainAccountId' + const doc = { + '@context': [ + 'https://www.w3.org/ns/did/v1', + { + blockchainAccountId: 'https://w3id.org/security#blockchainAccountId', + EcdsaSecp256k1RecoveryMethod2020: + 'https://identity.foundation/EcdsaSecp256k1RecoverySignature2020#EcdsaSecp256k1RecoveryMethod2020', + Ed25519VerificationKey2018: 'https://w3id.org/security#Ed25519VerificationKey2018', + }, + ], + id: did, + verificationMethod: [ + { + id: vmId, + type: 'EcdsaSecp256k1RecoveryMethod2020', + controller: did, + blockchainAccountId: accountId, + }, + ], + authentication: [vmId], + assertionMethod: [vmId], + } + if (SECPK1_NAMESPACES.includes(namespace)) { + // nothing to do here + } else if (namespace === TZ_NAMESPACE) { + (doc['@context'][1] as any).TezosMethod2021 = 'https://w3id.org/security#TezosMethod2021' + const tzId = did + '#TezosMethod2021' + doc.verificationMethod.push({ + id: tzId, + type: 'TezosMethod2021', + controller: did, + blockchainAccountId: accountId, + }) + doc.authentication.push(tzId) + doc.assertionMethod.push(tzId) + } else { + throw new Error(`chain namespace not supported ${namespace}`) + } + return doc +} + +export function getResolver(): ResolverRegistry { + return { + pkh: async ( + did: string, + parsed: ParsedDID, + r: Resolvable, + options: DIDResolutionOptions + ): Promise => { + const contentType = options.accept || DID_JSON + const response: DIDResolutionResult = { + didResolutionMetadata: { contentType }, + didDocument: null, + didDocumentMetadata: {}, + } + try { + const doc = toDidDoc(did, parsed.id) + if (contentType === DID_LD_JSON) { + response.didDocument = doc + } else if (contentType === DID_JSON) { + delete doc['@context'] + response.didDocument = doc + } else { + delete response.didResolutionMetadata.contentType + response.didResolutionMetadata.error = 'representationNotSupported' + } + } catch (e) { + response.didResolutionMetadata.error = 'invalidDid' + response.didResolutionMetadata.message = e.toString() + } + return response + }, + } +} \ No newline at end of file diff --git a/packages/did-provider-pkh/tsconfig.json b/packages/did-provider-pkh/tsconfig.json new file mode 100644 index 000000000..21035981a --- /dev/null +++ b/packages/did-provider-pkh/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.settings.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "build", + "declarationDir": "build", + "skipLibCheck": true + }, + "references": [{ "path": "../core" }, { "path": "../did-manager" }] +} diff --git a/packages/test-react-app/src/veramo/setup.ts b/packages/test-react-app/src/veramo/setup.ts index 32f7bfc93..e1b06dad0 100644 --- a/packages/test-react-app/src/veramo/setup.ts +++ b/packages/test-react-app/src/veramo/setup.ts @@ -28,6 +28,7 @@ import { VeramoEd25519Signature2018, } from '@veramo/credential-ld' import { getDidKeyResolver, KeyDIDProvider } from '@veramo/did-provider-key' +import { getDidPkhResolver, PkhDIDProvider } from '@veramo/did-provider-pkh' import { DIDComm, DIDCommMessageHandler, IDIDComm } from '@veramo/did-comm' import { ISelectiveDisclosure, SdrMessageHandler, SelectiveDisclosure } from '@veramo/selective-disclosure' import { KeyManagementSystem, SecretBox } from '@veramo/kms-local' @@ -64,6 +65,7 @@ export function getAgent(options?: IAgentOptions): TAgent { ...ethrDidResolver({ infuraProjectId: INFURA_PROJECT_ID }), ...webDidResolver(), ...getDidKeyResolver(), + ...getDidPkhResolver(), ...new FakeDidResolver(() => agent as TAgent).getDidFakeResolver(), }), }), @@ -106,6 +108,9 @@ export function getAgent(options?: IAgentOptions): TAgent { 'did:key': new KeyDIDProvider({ defaultKms: 'local', }), + 'did:pkh': new PkhDIDProvider({ + defaultKms: 'local', + }), 'did:fake': new FakeDidProvider(), }, }), diff --git a/packages/tsconfig.json b/packages/tsconfig.json index 9ca304380..7453d01a9 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -14,6 +14,7 @@ { "path": "did-manager" }, { "path": "did-provider-ethr" }, { "path": "did-provider-ion" }, + { "path": "did-provider-pkh" }, { "path": "did-provider-key" }, { "path": "did-provider-web" }, { "path": "did-resolver" }, diff --git a/scripts/prepare-react-test.sh b/scripts/prepare-react-test.sh index 36268181e..387f7e8f5 100644 --- a/scripts/prepare-react-test.sh +++ b/scripts/prepare-react-test.sh @@ -15,6 +15,7 @@ packages=( "did-manager" "did-provider-ethr" "did-provider-key" +"did-provider-pkh" "did-provider-web" "did-resolver" "key-manager" diff --git a/yarn.lock b/yarn.lock index bcbe233a1..37a085c52 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6186,6 +6186,11 @@ cacache@^16.0.0, cacache@^16.0.6, cacache@^16.1.0, cacache@^16.1.3: tar "^6.1.11" unique-filename "^2.0.0" +caip@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/caip/-/caip-1.1.0.tgz#0ccd5bf1bff822459389ccec0a8555712a30c374" + integrity sha512-yOO3Fu4ygyKYAdznuoaqschMKIZzcdgyMpBNtrIfrUhnOeaOWG+dh0c13wcOS6B/46IGGbncoyzJlio79jU7rw== + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -11399,6 +11404,15 @@ keccak@3.0.2: node-gyp-build "^4.2.0" readable-stream "^3.6.0" +keccak@^3.0.1, keccak@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.2.tgz#4c2c6e8c54e04f2670ee49fa734eb9da152206e0" + integrity sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ== + dependencies: + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + readable-stream "^3.6.0" + kind-of@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5"