Skip to content

Commit

Permalink
fix(did-provider-pkh): refactor and simplify did:pkh plugin (#1113)
Browse files Browse the repository at this point in the history
  • Loading branch information
dchagastelles authored Feb 7, 2023
1 parent 60bc5fd commit 42be48f
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 122 deletions.
188 changes: 111 additions & 77 deletions packages/did-provider-pkh/src/pkh-did-provider.ts
Original file line number Diff line number Diff line change
@@ -1,135 +1,169 @@
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<IKeyManager>;

const isIn = <T>(values: readonly T[], value: any): value is T => {
return values.includes(value);
};

type IContext = IAgentContext<IKeyManager>
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
*
* @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<Omit<IIdentifier, 'provider'>> {
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<IIdentifier, 'provider'> = {
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<IIdentifier, 'provider'> = {
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<IKeyManager>): Promise<IIdentifier> {
throw new Error('PkhDIDProvider updateIdentifier not supported yet.')

async updateIdentifier(
args: {
did: string;
kms?: string | undefined;
alias?: string | undefined;
options?: any;
},
context: IAgentContext<IKeyManager>
): Promise<IIdentifier> {
throw new Error('illegal_operation: did:pkh update is not possible.');
}

async deleteIdentifier(identifier: IIdentifier, context: IContext): Promise<boolean> {
async deleteIdentifier(
identifier: IIdentifier,
context: IContext
): Promise<boolean> {
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<any> {
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<any> {
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<any> {
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<any> {
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.');

}
}
82 changes: 37 additions & 45 deletions packages/did-provider-pkh/src/resolver.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
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',
{
blockchainAccountId: 'https://w3id.org/security#blockchainAccountId',
EcdsaSecp256k1RecoveryMethod2020:
'https://identity.foundation/EcdsaSecp256k1RecoverySignature2020#EcdsaSecp256k1RecoveryMethod2020',
Ed25519VerificationKey2018: 'https://w3id.org/security#Ed25519VerificationKey2018',
},
],
id: did,
Expand All @@ -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 {
Expand All @@ -65,28 +57,28 @@ export function getResolver(): ResolverRegistry {
r: Resolvable,
options: DIDResolutionOptions
): Promise<DIDResolutionResult> => {
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;
},
}
}
};
}

0 comments on commit 42be48f

Please sign in to comment.