From 42be48ffe2251510f7bd5e10b43362e816655eb9 Mon Sep 17 00:00:00 2001 From: dchagastelles Date: Tue, 7 Feb 2023 14:27:41 -0500 Subject: [PATCH] fix(did-provider-pkh): refactor and simplify did:pkh plugin (#1113) --- .../did-provider-pkh/src/pkh-did-provider.ts | 188 +++++++++++------- packages/did-provider-pkh/src/resolver.ts | 82 ++++---- 2 files changed, 148 insertions(+), 122 deletions(-) diff --git a/packages/did-provider-pkh/src/pkh-did-provider.ts b/packages/did-provider-pkh/src/pkh-did-provider.ts index 69697ca78..ef5485330 100644 --- a/packages/did-provider-pkh/src/pkh-did-provider.ts +++ b/packages/did-provider-pkh/src/pkh-did-provider.ts @@ -1,37 +1,52 @@ -import { IIdentifier, IKey, IService, IAgentContext, IKeyManager } from '@veramo/core' -import { computeAddress } from '@ethersproject/transactions' +import { computeAddress } from '@ethersproject/transactions'; +import { + IAgentContext, + IIdentifier, + IKey, + IKeyManager, + IService, + ManagedKeyInfo, +} from '@veramo/core'; + +import { AbstractIdentifierProvider } from '@veramo/did-manager'; +import Debug from 'debug' -import { AbstractIdentifierProvider } from '@veramo/did-manager' -import { computePublicKey } from '@ethersproject/signing-key' -import { BigNumber } from '@ethersproject/bignumber' +const debug = Debug('veramo:pkh-did-provider') -import Debug from 'debug' -const debug = Debug('veramo:did-pkh:identifier-provider') +type IContext = IAgentContext; + +const isIn = (values: readonly T[], value: any): value is T => { + return values.includes(value); +}; -type IContext = IAgentContext +export const SECPK1_NAMESPACES = ['eip155'] as const; +export const isValidNamespace = (x: string) => isIn(SECPK1_NAMESPACES, x); /** - * Options for creating a did:ethr + * Options for creating a did:pkh * @beta */ - export interface CreateDidPkhEthrOptions { +export interface CreateDidPkhOptions { + namespace: string; + privateKey: string; /** * This can be hex encoded chain ID (string) or a chainId number * * If this is not specified, `1` is assumed. */ - chainId?: string | number + 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) - } - +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 @@ -39,97 +54,116 @@ type IContext = IAgentContext * @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 - } - + private defaultKms: string; + private chainId: string; + constructor(options: { defaultKms: string; chainId?: string }) { + super(); + this.defaultKms = options.defaultKms; + this.chainId = options?.chainId ? options.chainId : '1'; + } async createIdentifier( - { kms, options }: { kms?: string; options?: CreateDidPkhEthrOptions }, - context: IContext, + { kms, options }: { kms?: string; options?: CreateDidPkhOptions }, + context: IContext ): Promise> { + const namespace = options?.namespace ? options.namespace : 'eip155'; - const key = await context.agent.keyManagerCreate({ kms: kms || this.defaultKms, type: 'Secp256k1' }) - const publicAddress = toEthereumAddress(key.publicKeyHex); - - const network = options?.chainId; - if (!network) { + if (!isValidNamespace(namespace)) { + debug( + `invalid_namespace: '${namespace}'. valid namespaces are: ${SECPK1_NAMESPACES}` + ); throw new Error( - `invalid_setup: Cannot create did:pkh. There is no known configuration for network=${network}'`, - ) + `invalid_namespace: '${namespace}'. valid namespaces are: ${SECPK1_NAMESPACES}` + ); } - const identifier: Omit = { - did: 'did:pkh:eip155:' + network + ':' + publicAddress, - controllerKeyId: key.kid, - keys: [key], - services: [], + let key: ManagedKeyInfo | null; + if (options?.privateKey !== undefined){ + key = await context.agent.keyManagerImport({ + kms: kms || this.defaultKms, + type: 'Secp256k1', + privateKeyHex: options?.privateKey as string, + }); + } else { + key = await context.agent.keyManagerCreate({ + kms: kms || this.defaultKms, + type: 'Secp256k1' + }); + } + const evmAddress: string = toEthereumAddress(key.publicKeyHex); + + if (key !== null) { + const identifier: Omit = { + did: 'did:pkh:' + namespace + ':' + this.chainId + ':' + evmAddress, + controllerKeyId: key.kid, + keys: [key], + services: [], + }; + return identifier; + } else { + debug('Could not create identifier due to some errors'); + throw new Error('unknown_error: could not create identifier due to errors creating or importing keys'); } - 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 updateIdentifier( + args: { + did: string; + kms?: string | undefined; + alias?: string | undefined; + options?: any; + }, + context: IAgentContext + ): Promise { + throw new Error('illegal_operation: did:pkh update is not possible.'); } - async deleteIdentifier(identifier: IIdentifier, context: IContext): Promise { + async deleteIdentifier( + identifier: IIdentifier, + context: IContext + ): Promise { for (const { kid } of identifier.keys) { - await context.agent.keyManagerDelete({ kid }) + await context.agent.keyManagerDelete({ kid }); } - return true + return true; } async addKey( - { identifier, key, options }: { identifier: IIdentifier; key: IKey; options?: any }, - context: IContext, + { + identifier, + key, + options, + }: { identifier: IIdentifier; key: IKey; options?: any }, + context: IContext ): Promise { - throw Error('PkhDIDProvider addKey not supported') + throw Error('illegal_operation: did:pkh addKey is not possible.'); } async addService( - { identifier, service, options }: { identifier: IIdentifier; service: IService; options?: any }, - context: IContext, + { + identifier, + service, + options, + }: { identifier: IIdentifier; service: IService; options?: any }, + context: IContext ): Promise { - throw Error('PkhDIDProvider addService not supported') + throw Error('illegal_operation: did:pkh addService is not possible.'); } async removeKey( args: { identifier: IIdentifier; kid: string; options?: any }, - context: IContext, + context: IContext ): Promise { - throw Error('PkhDIDProvider removeKey not supported') + throw Error('illegal_operation: did:pkh removeKey is not possible.'); + } async removeService( args: { identifier: IIdentifier; id: string; options?: any }, - context: IContext, + 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 - // } - + throw Error('illegal_operation: did:pkh removeService is not possible.'); + } } diff --git a/packages/did-provider-pkh/src/resolver.ts b/packages/did-provider-pkh/src/resolver.ts index 9ae477a17..2ae41f336 100644 --- a/packages/did-provider-pkh/src/resolver.ts +++ b/packages/did-provider-pkh/src/resolver.ts @@ -1,21 +1,22 @@ -import { AccountId, ChainIdParams } from 'caip' +import { AccountId, ChainIdParams } from 'caip'; import type { - DIDResolutionResult, DIDResolutionOptions, - ResolverRegistry, + DIDResolutionResult, ParsedDID, - 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' + Resolvable, + ResolverRegistry, +} from 'did-resolver'; +import { isValidNamespace, SECPK1_NAMESPACES } from './pkh-did-provider'; +import Debug from 'debug' +const debug = Debug('veramo:pkh-did-resolver') +const DID_LD_JSON = 'application/did+ld+json'; +const DID_JSON = 'application/did+json'; -function toDidDoc(did: string, accountId: string): any { - const { namespace } = AccountId.parse(accountId).chainId as ChainIdParams - const vmId = did + '#blockchainAccountId' +function toDidDoc(did: string, blockchainAccountId: string): any { + const { namespace } = AccountId.parse(blockchainAccountId) + .chainId as ChainIdParams; + const vmId = did + '#blockchainAccountId'; const doc = { '@context': [ 'https://www.w3.org/ns/did/v1', @@ -23,7 +24,6 @@ function toDidDoc(did: string, accountId: string): any { blockchainAccountId: 'https://w3id.org/security#blockchainAccountId', EcdsaSecp256k1RecoveryMethod2020: 'https://identity.foundation/EcdsaSecp256k1RecoverySignature2020#EcdsaSecp256k1RecoveryMethod2020', - Ed25519VerificationKey2018: 'https://w3id.org/security#Ed25519VerificationKey2018', }, ], id: did, @@ -32,29 +32,21 @@ function toDidDoc(did: string, accountId: string): any { id: vmId, type: 'EcdsaSecp256k1RecoveryMethod2020', controller: did, - blockchainAccountId: accountId, + blockchainAccountId, }, ], authentication: [vmId], assertionMethod: [vmId], + }; + if (!isValidNamespace(namespace)) { + debug( + `Invalid namespace '${namespace}'. Valid namespaces are: ${SECPK1_NAMESPACES}` + ); + throw new Error( + `illegal_argument: namespace '${namespace}' not supported. Valid namespaces are: ${SECPK1_NAMESPACES}` + ); } - 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 + return doc; } export function getResolver(): ResolverRegistry { @@ -65,28 +57,28 @@ export function getResolver(): ResolverRegistry { r: Resolvable, options: DIDResolutionOptions ): Promise => { - const contentType = options.accept || DID_JSON + const contentType = options.accept || DID_JSON; const response: DIDResolutionResult = { didResolutionMetadata: { contentType }, didDocument: null, didDocumentMetadata: {}, - } + }; try { - const doc = toDidDoc(did, parsed.id) + const doc = toDidDoc(did, parsed.id); if (contentType === DID_LD_JSON) { - response.didDocument = doc + response.didDocument = doc; } else if (contentType === DID_JSON) { - delete doc['@context'] - response.didDocument = doc + delete doc['@context']; + response.didDocument = doc; } else { - delete response.didResolutionMetadata.contentType - response.didResolutionMetadata.error = 'representationNotSupported' + delete response.didResolutionMetadata.contentType; + response.didResolutionMetadata.error = 'representationNotSupported'; } } catch (e) { - response.didResolutionMetadata.error = 'invalidDid' - response.didResolutionMetadata.message = e.toString() + response.didResolutionMetadata.error = 'invalidDid'; + response.didResolutionMetadata.message = (e as Error).message; } - return response + return response; }, - } -} \ No newline at end of file + }; +}