From 1d9f8d5d0e39fbeb01ef56fa57930313f0cdc579 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Thu, 27 May 2021 11:27:24 +0200 Subject: [PATCH 1/7] test(key-manager): add tests using testvectors for `keyManagerSign*` --- __tests__/shared/keyManager.ts | 70 +++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/__tests__/shared/keyManager.ts b/__tests__/shared/keyManager.ts index 246f7fc18..d74e9cfab 100644 --- a/__tests__/shared/keyManager.ts +++ b/__tests__/shared/keyManager.ts @@ -1,4 +1,5 @@ -import { TAgent, IDIDManager, IKeyManager, IAgentOptions } from '../../packages/core/src' +import { TAgent, IDIDManager, IKeyManager, IAgentOptions, TKeyType } from '../../packages/core/src' +import { serialize } from '@ethersproject/transactions' type ConfiguredAgent = TAgent @@ -218,5 +219,72 @@ export default (testContext: { // expect(decrypted).toEqual(message) // }) + + describe('using Secp256k1 testvectors', () => { + const importedKey = { + kid: + '04155ee0cbefeecd80de63a62b4ed8f0f97ac22a58f76a265903b9acab79bf018c7037e2bd897812170c92a4c978d6a10481491a37299d74c4bd412a111a4ac875', + kms: 'local', + type: 'Secp256k1', + publicKeyHex: + '04155ee0cbefeecd80de63a62b4ed8f0f97ac22a58f76a265903b9acab79bf018c7037e2bd897812170c92a4c978d6a10481491a37299d74c4bd412a111a4ac875', + privateKeyHex: '31d1ec15ff8110442012fef0d1af918c0e09b2e2ab821bba52ecc85f8655ec63', + } + + beforeAll(async () => { + const imported = await agent.keyManagerImport(importedKey) + }) + + it('should sign JWT using legacy method', async () => { + const signature = await agent.keyManagerSignJWT({ + kid: importedKey.kid, + data: 'bla.bla', + }) + expect(signature).toEqual( + 'pNAFkgmuKhqMbb_6Km--ZmY7UCkWunWUuNajSfF6rv5lEa5nNXCU7cnZBZVptU7u8h150qetqkqUaahAf-Cepw', + ) + }) + + it('should sign EthTX using legacy method', async () => { + const rawTx = await agent.keyManagerSignEthTX({ + kid: importedKey.kid, + transaction: { + to: '0xce31a19193d4b23f4e9d6163d7247243bAF801c3', + value: 300000, + gasLimit: 43092000, + gasPrice: 20000000000, + nonce: 1, + }, + }) + expect(rawTx).toEqual( + '0xf869018504a817c800840291882094ce31a19193d4b23f4e9d6163d7247243baf801c3830493e0801ba0f16e2206290181c3feaa04051dad19089105c24339dbdf0d80147b48a59fa152a0770e8751ec77ccc78e8b207023f168444f7cfb67055c55c70ef75234458a3d51', + ) + }) + }) + + describe('using Ed25519 testvectors', () => { + const importedKey = { + kid: 'ea75250531f6834328ac210618253288e4c54632962a9708ca82e4a399f79000', + kms: 'local', + type: 'Ed25519', + publicKeyHex: 'ea75250531f6834328ac210618253288e4c54632962a9708ca82e4a399f79000', + privateKeyHex: + '65f341541643070564bb48d9fc10556f2dec246fa056e436a8ec1cdef8c74766ea75250531f6834328ac210618253288e4c54632962a9708ca82e4a399f79000', + } + + beforeAll(async () => { + const imported = await agent.keyManagerImport(importedKey) + }) + + it('should sign JWT using legacy method', async () => { + const signature = await agent.keyManagerSignJWT({ + kid: importedKey.kid, + data: 'bla.bla', + }) + expect(signature).toEqual( + '_2P0iukN2CPH1nQ6LeBm1zQHHp3U4wSYDrpeWTWkp7yuzJex6O60Z4OhdfD5I9WPHV734US8n5vyD2VDbT1UCg', + ) + }) + }) }) } From 58589ff0299ab963f8dc2cba62162acaf6f41cef Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Wed, 19 May 2021 19:03:55 +0200 Subject: [PATCH 2/7] feat(key-manager): add generic signing capabilities --- __tests__/shared/keyManager.ts | 4 +- packages/core/plugin.schema.json | 76 ++++++++++-- packages/core/src/types/IIdentifier.ts | 9 +- packages/core/src/types/IKeyManager.ts | 40 ++++++- packages/credential-w3c/src/action-handler.ts | 4 +- packages/data-store/plugin.schema.json | 15 ++- packages/data-store/src/entities/key.ts | 16 ++- .../data-store/src/identifier/did-store.ts | 1 + .../src/ethr-did-provider.ts | 6 +- .../src/__tests__/integration.test.ts | 2 +- .../src/abstract-key-management-system.ts | 2 + packages/key-manager/src/key-manager.ts | 20 +++- packages/kms-local-react-native/package.json | 10 +- .../src/key-management-system.ts | 111 +++++++++++++++--- packages/kms-local/package.json | 12 +- .../kms-local/src/key-management-system.ts | 105 ++++++++++++++--- .../src/action-handler.ts | 12 +- yarn.lock | 61 ++-------- 18 files changed, 390 insertions(+), 116 deletions(-) diff --git a/__tests__/shared/keyManager.ts b/__tests__/shared/keyManager.ts index d74e9cfab..f6eb8b261 100644 --- a/__tests__/shared/keyManager.ts +++ b/__tests__/shared/keyManager.ts @@ -180,8 +180,8 @@ export default (testContext: { transaction: { to: '0xce31a19193d4b23f4e9d6163d7247243bAF801c3', value: 300000, - gas: 43092000, - gasPrice: '20000000000', + gasLimit: 43092000, + gasPrice: 20000000000, nonce: 1, }, }) diff --git a/packages/core/plugin.schema.json b/packages/core/plugin.schema.json index c8b87c794..51bce8ab5 100644 --- a/packages/core/plugin.schema.json +++ b/packages/core/plugin.schema.json @@ -349,7 +349,7 @@ "description": "Key Management System" }, "meta": { - "type": "object", + "$ref": "#/components/schemas/KeyMetadata", "description": "Optional. Key meta data" } }, @@ -367,6 +367,17 @@ ], "description": "Cryptographic key type" }, + "KeyMetadata": { + "type": "object", + "properties": { + "algorithms": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "IKey": { "type": "object", "properties": { @@ -393,13 +404,13 @@ "meta": { "anyOf": [ { - "type": "object" + "$ref": "#/components/schemas/KeyMetadata" }, { "type": "null" } ], - "description": "Optional. Key metadata. Can be used to store auth data to access remote kms" + "description": "Optional. Key metadata. This should be used to determine which algorithms are supported." } }, "required": [ @@ -470,13 +481,13 @@ "meta": { "anyOf": [ { - "type": "object" + "$ref": "#/components/schemas/KeyMetadata" }, { "type": "null" } ], - "description": "Optional. Key metadata. Can be used to store auth data to access remote kms" + "description": "Optional. Key metadata. This should be used to determine which algorithms are supported." } }, "required": [ @@ -511,6 +522,37 @@ ], "description": "Input arguments for {@link IKeyManager.keyManagerGet | keyManagerGet}" }, + "IKeyManagerSignArgs": { + "type": "object", + "properties": { + "kid": { + "type": "string", + "description": "Key ID" + }, + "alg": { + "type": "string", + "description": "The algorithm to use for signing. This must be one of the algorithms supported by the KMS for this key type." + }, + "data": { + "type": "string", + "description": "Data to sign" + }, + "enc": { + "type": "string", + "enum": [ + "utf-8", + "base16", + "base64" + ], + "description": "If the data is a \"string\" then you can specify which encoding is used. Default is \"utf-8\"" + } + }, + "required": [ + "kid", + "data" + ], + "description": "Input arguments for {@link IKeyManager.keyManagerSign | keyManagerSign}" + }, "IKeyManagerSignEthTXArgs": { "type": "object", "properties": { @@ -662,6 +704,15 @@ "type": "boolean" } }, + "keyManagerSign": { + "description": "Generates a signature according to the algorithm specified.", + "arguments": { + "$ref": "#/components/schemas/IKeyManagerSignArgs" + }, + "returnType": { + "type": "string" + } + }, "keyManagerSignEthTX": { "description": "Signs Ethereum transaction", "arguments": { @@ -734,13 +785,13 @@ "meta": { "anyOf": [ { - "type": "object" + "$ref": "#/components/schemas/KeyMetadata" }, { "type": "null" } ], - "description": "Optional. Key metadata. Can be used to store auth data to access remote kms" + "description": "Optional. Key metadata. This should be used to determine which algorithms are supported." } }, "required": [ @@ -759,6 +810,17 @@ ], "description": "Cryptographic key type" }, + "KeyMetadata": { + "type": "object", + "properties": { + "algorithms": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "IDIDManagerAddServiceArgs": { "type": "object", "properties": { diff --git a/packages/core/src/types/IIdentifier.ts b/packages/core/src/types/IIdentifier.ts index 111036370..0d306ad63 100644 --- a/packages/core/src/types/IIdentifier.ts +++ b/packages/core/src/types/IIdentifier.ts @@ -71,9 +71,14 @@ export interface IKey { privateKeyHex?: string /** - * Optional. Key metadata. Can be used to store auth data to access remote kms + * Optional. Key metadata. This should be used to determine which algorithms are supported. */ - meta?: object | null + meta?: KeyMetadata | null +} + +export interface KeyMetadata { + algorithms?: string[] + [x: string]: any } /** diff --git a/packages/core/src/types/IKeyManager.ts b/packages/core/src/types/IKeyManager.ts index c412d8a0c..35764f839 100644 --- a/packages/core/src/types/IKeyManager.ts +++ b/packages/core/src/types/IKeyManager.ts @@ -1,5 +1,5 @@ import { IPluginMethodMap } from './IAgent' -import { TKeyType, IKey } from './IIdentifier' +import { TKeyType, IKey, KeyMetadata } from './IIdentifier' /** * Input arguments for {@link IKeyManager.keyManagerCreate | keyManagerCreate} @@ -19,7 +19,7 @@ export interface IKeyManagerCreateArgs { /** * Optional. Key meta data */ - meta?: object + meta?: KeyMetadata } /** @@ -81,6 +81,35 @@ export interface IKeyManagerDecryptJWEArgs { data: string } +/** + * Input arguments for {@link IKeyManager.keyManagerSign | keyManagerSign} + * @public + */ +export interface IKeyManagerSignArgs { + /** + * Key ID + */ + kid: string + + /** + * The algorithm to use for signing. + * This must be one of the algorithms supported by the KMS for this key type. + */ + alg?: string + + /** + * Data to sign + */ + data: string + + /** + * If the data is a "string" then you can specify which encoding is used. Default is "utf-8" + */ + enc?: 'utf-8' | 'base16' | 'base64' + + [x: string]: any +} + /** * Input arguments for {@link IKeyManager.keyManagerSignJWT | keyManagerSignJWT} * @public @@ -143,6 +172,13 @@ export interface IKeyManager extends IPluginMethodMap { */ keyManagerImport(args: IKey): Promise + /** + * Generates a signature according to the algorithm specified. + * @throws `Error("not_supported")` if the KMS does not support the operation or if the key does not match the algorithm. + * @param args + */ + keyManagerSign(args: IKeyManagerSignArgs): Promise + /** * Encrypts data * @beta diff --git a/packages/credential-w3c/src/action-handler.ts b/packages/credential-w3c/src/action-handler.ts index b694c8ff5..075db1143 100644 --- a/packages/credential-w3c/src/action-handler.ts +++ b/packages/credential-w3c/src/action-handler.ts @@ -225,7 +225,7 @@ export class CredentialIssuer implements IAgentPlugin { const key = identifier.keys.find((k) => k.type === 'Secp256k1' || k.type === 'Ed25519') if (!key) throw Error('No signing key for ' + identifier.did) //FIXME: Throw an `unsupported_format` error if the `args.proofFormat` is not `jwt` - const signer = (data: string | Uint8Array) => context.agent.keyManagerSignJWT({ kid: key.kid, data }) + const signer = (data: string | Uint8Array) => context.agent.keyManagerSign({ kid: key.kid, data }) debug('Signing VP with', identifier.did) let alg = 'ES256K' if (key.type === 'Ed25519') { @@ -270,7 +270,7 @@ export class CredentialIssuer implements IAgentPlugin { const key = identifier.keys.find((k) => k.type === 'Secp256k1' || k.type === 'Ed25519') if (!key) throw Error('No signing key for ' + identifier.did) //FIXME: Throw an `unsupported_format` error if the `args.proofFormat` is not `jwt` - const signer = (data: string | Uint8Array) => context.agent.keyManagerSignJWT({ kid: key.kid, data }) + const signer = (data: string | Uint8Array) => context.agent.keyManagerSign({ kid: key.kid, data }) debug('Signing VC with', identifier.did) let alg = 'ES256K' diff --git a/packages/data-store/plugin.schema.json b/packages/data-store/plugin.schema.json index 2c48c5231..565b56e2a 100644 --- a/packages/data-store/plugin.schema.json +++ b/packages/data-store/plugin.schema.json @@ -151,13 +151,13 @@ "meta": { "anyOf": [ { - "type": "object" + "$ref": "#/components/schemas/KeyMetadata" }, { "type": "null" } ], - "description": "Optional. Key metadata. Can be used to store auth data to access remote kms" + "description": "Optional. Key metadata. This should be used to determine which algorithms are supported." } }, "required": [ @@ -176,6 +176,17 @@ ], "description": "Cryptographic key type" }, + "KeyMetadata": { + "type": "object", + "properties": { + "algorithms": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "IService": { "type": "object", "properties": { diff --git a/packages/data-store/src/entities/key.ts b/packages/data-store/src/entities/key.ts index 4777bbcef..010a0196b 100644 --- a/packages/data-store/src/entities/key.ts +++ b/packages/data-store/src/entities/key.ts @@ -1,3 +1,4 @@ +import { KeyMetadata } from '@veramo/core' import { Entity, Column, PrimaryColumn, BaseEntity, ManyToOne } from 'typeorm' import { Identifier } from './identifier' @@ -24,8 +25,19 @@ export class Key extends BaseEntity { @Column({ nullable: true }) privateKeyHex?: string - @Column({ type: 'simple-json', nullable: true }) - meta?: object | null + @Column({ + type: 'simple-json', + nullable: true, + transformer: { + to: (value: any): KeyMetadata | null => { + return value + }, + from: (value: KeyMetadata | null): object | null => { + return value + }, + }, + }) + meta?: KeyMetadata | null @ManyToOne((type) => Identifier, (identifier) => identifier.keys) //@ts-ignore diff --git a/packages/data-store/src/identifier/did-store.ts b/packages/data-store/src/identifier/did-store.ts index 11bd4e8cd..f25365ac1 100644 --- a/packages/data-store/src/identifier/did-store.ts +++ b/packages/data-store/src/identifier/did-store.ts @@ -47,6 +47,7 @@ export class DIDStore extends AbstractDIDStore { type: k.type, kms: k.kms, publicKeyHex: k.publicKeyHex, + meta: k.meta })), } if (identifier.alias) { diff --git a/packages/did-provider-ethr/src/ethr-did-provider.ts b/packages/did-provider-ethr/src/ethr-did-provider.ts index 04aa0e7be..dac28633d 100644 --- a/packages/did-provider-ethr/src/ethr-did-provider.ts +++ b/packages/did-provider-ethr/src/ethr-did-provider.ts @@ -108,11 +108,11 @@ export class EthrDIDProvider extends AbstractIdentifierProvider { const attribute = 'did/pub/' + key.type + '/' + usg + '/hex' const value = '0x' + key.publicKeyHex const ttl = options?.ttl || this.ttl - const gas = options?.gas || this.gas + const gasLimit = options?.gas || this.gas - debug('ethrDid.setAttribute %o', { attribute, value, ttl, gas }) + debug('ethrDid.setAttribute %o', { attribute, value, ttl, gas: gasLimit }) - const txHash = await ethrDid.setAttribute(attribute, value, ttl, gas) + const txHash = await ethrDid.setAttribute(attribute, value, ttl, gasLimit) debug({ txHash }) return txHash } diff --git a/packages/did-resolver/src/__tests__/integration.test.ts b/packages/did-resolver/src/__tests__/integration.test.ts index f45b997ab..ed2a5409d 100644 --- a/packages/did-resolver/src/__tests__/integration.test.ts +++ b/packages/did-resolver/src/__tests__/integration.test.ts @@ -32,7 +32,7 @@ describe('@veramo/did-resolver', () => { expect.assertions(1) await expect(resolverPlugin.resolveDid({ didUrl: 'did:web:did.actor:alice' })).resolves.toEqual({ didDocument: { - '@context': ["https://w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2018/v1"], + '@context': ['https://w3.org/ns/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], id: 'did:web:did.actor:alice', publicKey: [ { diff --git a/packages/key-manager/src/abstract-key-management-system.ts b/packages/key-manager/src/abstract-key-management-system.ts index 1f92fd86c..defe16b90 100644 --- a/packages/key-manager/src/abstract-key-management-system.ts +++ b/packages/key-manager/src/abstract-key-management-system.ts @@ -7,4 +7,6 @@ export abstract class AbstractKeyManagementSystem { abstract decryptJWE(args: { key: IKey; data: string }): Promise abstract signJWT(args: { key: IKey; data: string | Uint8Array }): Promise abstract signEthTX(args: { key: IKey; transaction: object }): Promise + + abstract sign(args: { key: IKey; alg?: string; data: Uint8Array; [x: string]: any }): Promise } diff --git a/packages/key-manager/src/key-manager.ts b/packages/key-manager/src/key-manager.ts index 7a03b1f6c..c0e695688 100644 --- a/packages/key-manager/src/key-manager.ts +++ b/packages/key-manager/src/key-manager.ts @@ -12,7 +12,9 @@ import { IKeyManagerSignJWTArgs, IKeyManagerSignEthTXArgs, schema, + IKeyManagerSignArgs, } from '@veramo/core' +import * as u8a from 'uint8arrays' /** * Agent plugin that provides {@link @veramo/core#IKeyManager} methods @@ -43,6 +45,7 @@ export class KeyManager implements IAgentPlugin { keyManagerDecryptJWE: this.keyManagerDecryptJWE.bind(this), keyManagerSignJWT: this.keyManagerSignJWT.bind(this), keyManagerSignEthTX: this.keyManagerSignEthTX.bind(this), + keyManagerSign: this.keyManagerSign.bind(this), } } @@ -105,12 +108,27 @@ export class KeyManager implements IAgentPlugin { } /** {@inheritDoc @veramo/core#IKeyManager.keyManagerSignJWT} */ - async keyManagerSignJWT({ kid, data }: IKeyManagerSignJWTArgs): Promise< string> { + async keyManagerSignJWT({ kid, data }: IKeyManagerSignJWTArgs): Promise { const key = await this.store.get({ kid }) const kms = this.getKms(key.kms) return kms.signJWT({ key, data }) } + /** {@inheritDoc @veramo/core#IKeyManager.keyManagerSign} */ + async keyManagerSign(args: IKeyManagerSignArgs): Promise { + const { kid, data, alg, enc, extras } = args + const key = await this.store.get({ kid }) + let dataBytes + const encoding = enc || 'utf-8' + if (typeof data === 'string') { + dataBytes = u8a.fromString(data, encoding) + } else { + dataBytes = data + } + const kms = this.getKms(key.kms) + return kms.sign({ key, alg, data: dataBytes, ...extras }) + } + /** {@inheritDoc @veramo/core#IKeyManager.keyManagerSignEthTX} */ async keyManagerSignEthTX({ kid, transaction }: IKeyManagerSignEthTXArgs): Promise { const key = await this.store.get({ kid }) diff --git a/packages/kms-local-react-native/package.json b/packages/kms-local-react-native/package.json index 164fc47ed..d19b8701e 100644 --- a/packages/kms-local-react-native/package.json +++ b/packages/kms-local-react-native/package.json @@ -9,20 +9,24 @@ "extract-api": "yarn veramo dev extract-api" }, "dependencies": { + "@ethersproject/abstract-provider": "^5.1.0", + "@ethersproject/abstract-signer": "^5.1.0", + "@ethersproject/bytes": "^5.1.0", + "@ethersproject/strings": "^5.1.0", + "@ethersproject/transactions": "^5.1.1", + "@ethersproject/wallet": "^5.1.0", "@veramo/core": "^1.2.0", "@veramo/key-manager": "^1.2.0", "base-58": "^0.0.1", "debug": "^4.1.1", "did-jwt": "5.5.2", - "elliptic": "6.5.4", - "ethjs-signer": "^0.1.1" + "uint8arrays": "^2.1.5" }, "peerDependencies": { "react-native-sodium": "*" }, "devDependencies": { "@types/debug": "4.1.5", - "@types/ethjs-signer": "0.1.0", "react-native-sodium": "0.3.9", "typescript": "4.3.2" }, diff --git a/packages/kms-local-react-native/src/key-management-system.ts b/packages/kms-local-react-native/src/key-management-system.ts index a83212401..7c46aeca2 100644 --- a/packages/kms-local-react-native/src/key-management-system.ts +++ b/packages/kms-local-react-native/src/key-management-system.ts @@ -1,12 +1,16 @@ import { TKeyType, IKey } from '@veramo/core' import { AbstractKeyManagementSystem } from '@veramo/key-manager' -import sodium from 'react-native-sodium' -import { EdDSASigner, ES256KSigner, NaclSigner } from 'did-jwt' -const EC = require('elliptic').ec -const secp256k1 = new EC('secp256k1') +import sodium from 'libsodium-wrappers' +import { EdDSASigner, ES256KSigner } from 'did-jwt' +import { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer' +import { TransactionRequest } from '@ethersproject/abstract-provider' +import { arrayify } from '@ethersproject/bytes' +import { toUtf8String } from '@ethersproject/strings' +import { serialize, parse } from '@ethersproject/transactions' +import { Wallet } from '@ethersproject/wallet' +import * as u8a from 'uint8arrays' import { DIDComm } from './didcomm' const didcomm = new DIDComm() -import { sign } from 'ethjs-signer' import Debug from 'debug' const debug = Debug('veramo:react-native-libsodium:kms') @@ -23,15 +27,23 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { kid: Buffer.from(keyPairEd25519.publicKey).toString('hex'), publicKeyHex: Buffer.from(keyPairEd25519.publicKey).toString('hex'), privateKeyHex: Buffer.from(keyPairEd25519.privateKey).toString('hex'), + meta: { + algorithms: ['Ed25519', 'EdDSA'], + }, } break case 'Secp256k1': - const keyPairSecp256k1 = secp256k1.genKeyPair() + const keyPair = Wallet.createRandom()._signingKey() + const publicKeyHex = keyPair.publicKey.substring(2) + const privateKeyHex = keyPair.privateKey.substring(2) key = { type, - kid: keyPairSecp256k1.getPublic('hex'), - publicKeyHex: keyPairSecp256k1.getPublic('hex'), - privateKeyHex: keyPairSecp256k1.getPrivate('hex'), + kid: publicKeyHex, + publicKeyHex, + privateKeyHex, + meta: { + algorithms: ['ES256K', 'ES256K-R', 'eth_signTransaction', 'eth_signTypedData', 'eth_signMessage'], + }, } break default: @@ -68,21 +80,86 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { return unpackMessage.message } + /**@deprecated please use `sign({key, alg: 'eth_signTransaction', data: arrayify(serialize(transaction))})` instead */ async signEthTX({ key, transaction }: { key: IKey; transaction: object }): Promise { - return sign(transaction, '0x' + key.privateKeyHex) + const data = arrayify(serialize(transaction)) + const alg = 'eth_signTransaction' + return this.sign({ key, data, alg }) } - async signJWT({ key, data }: { key: IKey; data: string }): Promise< string> { + /**@deprecated please use `sign({key, data})` instead, with `Uint8Array` data */ + async signJWT({ key, data }: { key: IKey; data: string | Uint8Array }): Promise { + let dataBytes: Uint8Array + if (typeof data === 'string') { + try { + dataBytes = arrayify(data, { allowMissingPrefix: true }) + } catch (e) { + dataBytes = u8a.fromString(data, 'utf-8') + } + } else { + dataBytes = data + } + return this.sign({ key, data: dataBytes }) + } + + async sign({ + key, + alg, + data, + extras, + }: { + key: IKey + alg?: string + data: Uint8Array + extras?: KMSSignerExtras + }): Promise { + //FIXME: KMS implementation should not rely on private keys being provided, but rather manage their own keys if (!key.privateKeyHex) throw Error('No private key for kid: ' + key.kid) - if (key.type === 'Ed25519') { + if (key.type === 'Ed25519' && (typeof alg === 'undefined' || ['Ed25519', 'EdDSA'].includes(alg))) { const signer = EdDSASigner(key.privateKeyHex) - return (signer(data) as any) as string + const signature = await signer(data) + return signature as string } else if (key.type === 'Secp256k1') { - const signer = ES256KSigner(key.privateKeyHex) - return signer(data) as unknown as string - } else { - throw Error('Cannot sign JWT with key of type ' + key.type) + if (typeof alg === 'undefined' || ['ES256K', 'ES256K-R'].includes(alg)) { + const signer = ES256KSigner(key.privateKeyHex, alg === 'ES256K-R') + const signature = await signer(data) + return signature as string + } else if (['eth_signTransaction', 'signTransaction', 'signTx'].includes(alg)) { + const tx = parse(data) + const wallet = new Wallet(key.privateKeyHex) + const signature = await wallet.signTransaction(tx as any) + return signature + } else if (alg === 'eth_signMessage') { + const wallet = new Wallet(key.privateKeyHex) + const signature = await wallet.signMessage(data) + return signature + } else if (['eth_signTypedData', 'EthereumEip712Signature2021'].includes(alg)) { + let msg, msgDomain, msgTypes + const serializedData = toUtf8String(data) + let jsonData = JSON.parse(serializedData) + if (typeof jsonData.domain === 'object' && typeof jsonData.types === 'object') { + const { domain = undefined, types = undefined, message = undefined } = { ...extras, ...jsonData } + msg = message || jsonData + msgDomain = domain + msgTypes = types + } + if (typeof msgDomain !== 'object' || typeof msgTypes !== 'object') { + throw Error(`invalid_arguments: Cannot sign typed data. 'domain' and 'types' must be provided`) + } + const wallet = new Wallet(key.privateKeyHex) + + const signature = await wallet._signTypedData(msgDomain, msgTypes, msg) + return signature + } } + throw Error(`not_supported: Cannot sign ${alg} using key of type ${key.type}`) } } + +interface KMSSignerExtras { + domain?: TypedDataDomain + types?: Record + transaction?: TransactionRequest + [x: string]: any +} diff --git a/packages/kms-local/package.json b/packages/kms-local/package.json index a8cba441c..9a3b93326 100644 --- a/packages/kms-local/package.json +++ b/packages/kms-local/package.json @@ -9,18 +9,22 @@ "extract-api": "yarn veramo dev extract-api" }, "dependencies": { + "@ethersproject/abstract-provider": "^5.1.0", + "@ethersproject/abstract-signer": "^5.1.0", + "@ethersproject/bytes": "^5.1.0", + "@ethersproject/strings": "^5.1.0", + "@ethersproject/transactions": "^5.1.1", + "@ethersproject/wallet": "^5.1.0", "@veramo/core": "^1.2.0", "@veramo/key-manager": "^1.2.0", "base-58": "^0.0.1", "debug": "^4.1.1", "did-jwt": "5.5.2", - "elliptic": "6.5.4", - "ethjs-signer": "^0.1.1", - "libsodium-wrappers": "^0.7.8" + "libsodium-wrappers": "^0.7.8", + "uint8arrays": "^2.1.5" }, "devDependencies": { "@types/debug": "4.1.5", - "@types/ethjs-signer": "0.1.0", "@types/libsodium-wrappers": "0.7.9", "typescript": "4.3.2" }, diff --git a/packages/kms-local/src/key-management-system.ts b/packages/kms-local/src/key-management-system.ts index 1ccbdfb48..67b93a62f 100644 --- a/packages/kms-local/src/key-management-system.ts +++ b/packages/kms-local/src/key-management-system.ts @@ -2,11 +2,15 @@ import { TKeyType, IKey } from '@veramo/core' import { AbstractKeyManagementSystem } from '@veramo/key-manager' import sodium from 'libsodium-wrappers' import { EdDSASigner, ES256KSigner } from 'did-jwt' -const EC = require('elliptic').ec -const secp256k1 = new EC('secp256k1') +import { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer' +import { TransactionRequest } from '@ethersproject/abstract-provider' +import { arrayify } from '@ethersproject/bytes' +import { toUtf8String } from '@ethersproject/strings' +import { serialize, parse } from '@ethersproject/transactions' +import { Wallet } from '@ethersproject/wallet' +import * as u8a from 'uint8arrays' import { DIDComm } from './didcomm' const didcomm = new DIDComm() -import { sign } from 'ethjs-signer' import Debug from 'debug' const debug = Debug('veramo:sodium:kms') @@ -23,15 +27,23 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { kid: Buffer.from(keyPairEd25519.publicKey).toString('hex'), publicKeyHex: Buffer.from(keyPairEd25519.publicKey).toString('hex'), privateKeyHex: Buffer.from(keyPairEd25519.privateKey).toString('hex'), + meta: { + algorithms: ['Ed25519', 'EdDSA'], + }, } break case 'Secp256k1': - const keyPairSecp256k1 = secp256k1.genKeyPair() + const keyPair = Wallet.createRandom()._signingKey() + const publicKeyHex = keyPair.publicKey.substring(2) + const privateKeyHex = keyPair.privateKey.substring(2) key = { type, - kid: keyPairSecp256k1.getPublic('hex'), - publicKeyHex: keyPairSecp256k1.getPublic('hex'), - privateKeyHex: keyPairSecp256k1.getPrivate('hex'), + kid: publicKeyHex, + publicKeyHex, + privateKeyHex, + meta: { + algorithms: ['ES256K', 'ES256K-R', 'eth_signTransaction', 'eth_signTypedData', 'eth_signMessage'], + }, } break default: @@ -68,21 +80,86 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { return unpackMessage.message } + /**@deprecated please use `sign({key, alg: 'eth_signTransaction', data: arrayify(serialize(transaction))})` instead */ async signEthTX({ key, transaction }: { key: IKey; transaction: object }): Promise { - return sign(transaction, '0x' + key.privateKeyHex) + const data = arrayify(serialize(transaction)) + const alg = 'eth_signTransaction' + return this.sign({ key, data, alg }) } + /**@deprecated please use sign() instead */ async signJWT({ key, data }: { key: IKey; data: string | Uint8Array }): Promise { + let dataBytes: Uint8Array + if (typeof data === 'string') { + try { + dataBytes = arrayify(data, { allowMissingPrefix: true }) + } catch (e) { + dataBytes = u8a.fromString(data, 'utf-8') + } + } else { + dataBytes = data + } + return this.sign({ key, data: dataBytes }) + } + + async sign({ + key, + alg, + data, + extras, + }: { + key: IKey + alg?: string + data: Uint8Array + extras?: KMSSignerExtras + }): Promise { + //FIXME: KMS implementation should not rely on private keys being provided, but rather manage their own keys if (!key.privateKeyHex) throw Error('No private key for kid: ' + key.kid) - if (key.type === 'Ed25519') { + if (key.type === 'Ed25519' && (typeof alg === 'undefined' || ['Ed25519', 'EdDSA'].includes(alg))) { const signer = EdDSASigner(key.privateKeyHex) - return (signer(data) as any) as string + const signature = await signer(data) + return signature as string } else if (key.type === 'Secp256k1') { - const signer = ES256KSigner(key.privateKeyHex) - return (signer(data) as any) as string - } else { - throw Error('Cannot sign JWT with key of type ' + key.type) + if (typeof alg === 'undefined' || ['ES256K', 'ES256K-R'].includes(alg)) { + const signer = ES256KSigner(key.privateKeyHex, alg === 'ES256K-R') + const signature = await signer(data) + return signature as string + } else if (['eth_signTransaction', 'signTransaction', 'signTx'].includes(alg)) { + const tx = parse(data) + const wallet = new Wallet(key.privateKeyHex) + const signature = await wallet.signTransaction(tx as any) + return signature + } else if (alg === 'eth_signMessage') { + const wallet = new Wallet(key.privateKeyHex) + const signature = await wallet.signMessage(data) + return signature + } else if (['eth_signTypedData', 'EthereumEip712Signature2021'].includes(alg)) { + let msg, msgDomain, msgTypes + const serializedData = toUtf8String(data) + let jsonData = JSON.parse(serializedData) + if (typeof jsonData.domain === 'object' && typeof jsonData.types === 'object') { + const { domain = undefined, types = undefined, message = undefined } = { ...extras, ...jsonData } + msg = message || jsonData + msgDomain = domain + msgTypes = types + } + if (typeof msgDomain !== 'object' || typeof msgTypes !== 'object') { + throw Error(`invalid_arguments: Cannot sign typed data. 'domain' and 'types' must be provided`) + } + const wallet = new Wallet(key.privateKeyHex) + + const signature = await wallet._signTypedData(msgDomain, msgTypes, msg) + return signature + } } + throw Error(`not_supported: Cannot sign ${alg} using key of type ${key.type}`) } } + +interface KMSSignerExtras { + domain?: TypedDataDomain + types?: Record + transaction?: TransactionRequest + [x: string]: any +} diff --git a/packages/selective-disclosure/src/action-handler.ts b/packages/selective-disclosure/src/action-handler.ts index 9e78bcb22..a3d66a0ec 100644 --- a/packages/selective-disclosure/src/action-handler.ts +++ b/packages/selective-disclosure/src/action-handler.ts @@ -67,7 +67,17 @@ export class SelectiveDisclosure implements IAgentPlugin { const key = identifier.keys.find((k) => k.type === 'Secp256k1') if (!key) throw Error('Signing key not found') - const signer = (data: string | Uint8Array) => context.agent.keyManagerSignJWT({ kid: key.kid, data }) + const signer = (data: string | Uint8Array) => { + let dataString, enc: 'base16' | undefined + if (typeof(data) === 'string') { + dataString = data + enc = undefined + } else { + dataString = Buffer.from(data).toString("hex"), + enc = 'base16' + } + return context.agent.keyManagerSign({ kid: key.kid, data: dataString, enc }) + } const jwt = await createJWT( { type: 'sdr', diff --git a/yarn.lock b/yarn.lock index 4e13e1bbc..7c14b31bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2727,13 +2727,6 @@ resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== -"@types/ethjs-signer@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@types/ethjs-signer/-/ethjs-signer-0.1.0.tgz#0960ba391a254d51dbef83f423dc7efa1cd081c4" - integrity sha512-Bom/6SlljFQexWsxYnMLcR18kQxUcqqfXWct7c1ZMNXYzyNNsF5RP60x7cdAhURnlYEmiTGCTO58YEhe063YbA== - dependencies: - "@types/node" "*" - "@types/express-serve-static-core@*": version "4.17.16" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.16.tgz#56626e2f60bdd9a5193bc84a1042a506eeb69da7" @@ -3650,7 +3643,7 @@ braces@^3.0.1: dependencies: fill-range "^7.0.1" -brorand@^1.0.1, brorand@^1.1.0: +brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= @@ -5036,21 +5029,6 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.723: - version "1.3.741" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.741.tgz#dc1024b19b31e27fb2c8c0a1f120cb05fc6ca695" - integrity sha512-4i3T0cwnHo1O4Mnp9JniEco8bZiXoqbm3PhW5hv7uu8YLg35iajYrRnNyKFaN8/8SSTskU2hYqVTeYVPceSpUA== - -elliptic@6.3.2: - version "6.3.2" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.2.tgz#e4c81e0829cf0a65ab70e998b8232723b5c1bc48" - integrity sha1-5MgeCCnPCmWrcOmYuCMnI7XBvEg= - dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" - hash.js "^1.0.0" - inherits "^2.0.1" - elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -5270,17 +5248,6 @@ ethjs-schema@0.1.4: resolved "https://registry.yarnpkg.com/ethjs-schema/-/ethjs-schema-0.1.4.tgz#0323a16333b1ace9a8f1d696a6ee63448fdd455f" integrity sha1-AyOhYzOxrOmo8daWpu5jRI/dRV8= -ethjs-signer@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ethjs-signer/-/ethjs-signer-0.1.1.tgz#0af77961e29ee458603aabd3660b8868d3386441" - integrity sha1-Cvd5YeKe5FhgOqvTZguIaNM4ZEE= - dependencies: - elliptic "6.3.2" - js-sha3 "0.5.5" - number-to-bn "1.1.0" - rlp "2.0.0" - strip-hex-prefix "1.0.0" - ethjs-util@0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.3.tgz#dfd5ea4a400dc5e421a889caf47e081ada78bb55" @@ -7400,11 +7367,6 @@ jju@~1.4.0: resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" integrity sha1-o6vicYryQaKykE+EpiWXDzia4yo= -js-sha3@0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.5.tgz#baf0c0e8c54ad5903447df96ade7a4a1bca79a4a" - integrity sha1-uvDA6MVK1ZA0R9+Wreekobynmko= - js-sha3@0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" @@ -9212,15 +9174,6 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -number-to-bn@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.1.0.tgz#51a3387c5bc68035ab4058c626132f767d9d08bf" - integrity sha1-UaM4fFvGgDWrQFjGJhMvdn2dCL8= - dependencies: - bn.js "4.11.6" - is-hex-prefixed "1.0.0" - strip-hex-prefix "1.0.0" - number-to-bn@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" @@ -10630,11 +10583,6 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" -rlp@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.0.0.tgz#9db384ff4b89a8f61563d92395d8625b18f3afb0" - integrity sha1-nbOE/0uJqPYVY9kjldhiWxjzr7A= - rsvp@^3.5.0: version "3.6.2" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" @@ -11934,6 +11882,13 @@ uint8arrays@^2.1.3: multibase "^4.0.1" web-encoding "^1.1.0" +uint8arrays@^2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-2.1.5.tgz#9e6e6377a9463d5eba4620a3f0450f7eb389a351" + integrity sha512-CSR7AO+4AHUeSOnZ/NBNCElDeWfRh9bXtOck27083kc7SznmmHIhNEkEOCQOn0wvrIMjS3IH0TNLR16vuc46mA== + dependencies: + multibase "^4.0.1" + umask@^1.1.0, umask@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" From f465e0909580aad7523d35e633a3250c174ff4ac Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Thu, 20 May 2021 17:15:02 +0200 Subject: [PATCH 3/7] feat(did-provider-ethr): use generic signer in did:ethr provider --- .vscode/settings.json | 5 +- packages/did-provider-ethr/package.json | 10 +- .../src/ethr-did-provider.ts | 94 ++++++++----------- .../did-provider-ethr/src/kms-eth-signer.ts | 53 +++++++++++ .../kms-local/src/key-management-system.ts | 5 + yarn.lock | 77 --------------- 6 files changed, 108 insertions(+), 136 deletions(-) create mode 100644 packages/did-provider-ethr/src/kms-eth-signer.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 3662b3700..5bb39d90d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "cSpell.words": [ + "ethersproject" + ] } \ No newline at end of file diff --git a/packages/did-provider-ethr/package.json b/packages/did-provider-ethr/package.json index 4049d2565..802a06298 100644 --- a/packages/did-provider-ethr/package.json +++ b/packages/did-provider-ethr/package.json @@ -9,13 +9,17 @@ "extract-api": "yarn veramo dev extract-api" }, "dependencies": { + "@ethersproject/abstract-provider": "^5.1.0", + "@ethersproject/abstract-signer": "^5.1.0", + "@ethersproject/address": "^5.1.0", + "@ethersproject/bytes": "^5.1.0", + "@ethersproject/properties": "^5.1.0", "@ethersproject/signing-key": "^5.1.0", + "@ethersproject/transactions": "^5.1.1", "@veramo/core": "^1.2.0", "@veramo/did-manager": "^1.2.0", "debug": "^4.1.1", - "ethjs-provider-signer": "^0.1.4", - "ethr-did": "2.1.4", - "js-sha3": "^0.8.0" + "ethr-did": "2.1.4" }, "devDependencies": { "@types/debug": "4.1.5", diff --git a/packages/did-provider-ethr/src/ethr-did-provider.ts b/packages/did-provider-ethr/src/ethr-did-provider.ts index dac28633d..9703a0edc 100644 --- a/packages/did-provider-ethr/src/ethr-did-provider.ts +++ b/packages/did-provider-ethr/src/ethr-did-provider.ts @@ -1,22 +1,22 @@ import { IIdentifier, IKey, IService, IAgentContext, IKeyManager } from '@veramo/core' import { AbstractIdentifierProvider } from '@veramo/did-manager' -import { keccak_256 } from 'js-sha3' +import { Provider } from '@ethersproject/abstract-provider' +import { JsonRpcProvider } from '@ethersproject/providers' +import { computePublicKey } from '@ethersproject/signing-key' +import { computeAddress } from '@ethersproject/transactions' +import { KmsEthereumSigner } from './kms-eth-signer' import Debug from 'debug' import { EthrDID } from 'ethr-did' -import { computePublicKey } from '@ethersproject/signing-key' -const SignerProvider = require('ethjs-provider-signer') const debug = Debug('veramo:did-provider-ethr') -type IContext = IAgentContext +export type IRequiredContext = IAgentContext /** * Helper method that can computes the ethereumAddress corresponding to a secp256k1 public key. * @param hexPublicKey A hex encoded public key, prefixed with `0x` */ export function toEthereumAddress(hexPublicKey: string): string { - return `0x${Buffer.from(keccak_256.arrayBuffer(Buffer.from(hexPublicKey.slice(2), 'hex'))) - .slice(-20) - .toString('hex')}` + return computeAddress('0x' + hexPublicKey) } /** @@ -26,7 +26,7 @@ export function toEthereumAddress(hexPublicKey: string): string { export class EthrDIDProvider extends AbstractIdentifierProvider { private defaultKms: string private network: string - private web3Provider?: any + private web3Provider?: Provider private rpcUrl?: string private gas?: number private ttl?: number @@ -36,7 +36,7 @@ export class EthrDIDProvider extends AbstractIdentifierProvider { defaultKms: string network: string rpcUrl?: string - web3Provider?: object + web3Provider?: Provider ttl?: number gas?: number registry?: string @@ -45,7 +45,10 @@ export class EthrDIDProvider extends AbstractIdentifierProvider { this.defaultKms = options.defaultKms this.network = options.network this.rpcUrl = options.rpcUrl - this.web3Provider = options.web3Provider + this.web3Provider = options.web3Provider + if (typeof this.web3Provider === 'undefined') { + this.web3Provider = new JsonRpcProvider(this.rpcUrl, this.network) + } this.ttl = options.ttl this.gas = options.gas this.registry = options.registry @@ -53,7 +56,7 @@ export class EthrDIDProvider extends AbstractIdentifierProvider { async createIdentifier( { kms, options }: { kms?: string; options?: any }, - context: IContext, + context: IRequiredContext, ): Promise> { const key = await context.agent.keyManagerCreate({ kms: kms || this.defaultKms, type: 'Secp256k1' }) const compressedPublicKey = computePublicKey(`0x${key.publicKeyHex}`, true) @@ -67,43 +70,36 @@ export class EthrDIDProvider extends AbstractIdentifierProvider { return identifier } - async deleteIdentifier(identifier: IIdentifier, context: IContext): Promise { + async deleteIdentifier(identifier: IIdentifier, context: IRequiredContext): Promise { for (const { kid } of identifier.keys) { await context.agent.keyManagerDelete({ kid }) } return true } - private getWeb3Provider({ controllerKeyId }: IIdentifier, context: IContext) { - if (!this.web3Provider && !this.rpcUrl) throw Error('Web3Provider or rpcUrl required') - if (!controllerKeyId) throw Error('ControllerKeyId does not exist') - - const web3Provider = - this.web3Provider || - new SignerProvider(this.rpcUrl, { - signTransaction: ( - transaction: object, - callback: (error: string | null, signature?: string) => void, - ) => { - context.agent - .keyManagerSignEthTX({ kid: controllerKeyId, transaction }) - .then((signature) => callback(null, signature)) - .catch((error) => callback(error)) - }, - }) - return web3Provider + private async getEthrDidController(identifier: IIdentifier, context: IRequiredContext) { + if (identifier.controllerKeyId == null) { + throw new Error('invalid_argument: identifier does not list a `controllerKeyId`') + } + const controllerKey = await context.agent.keyManagerGet({ kid: identifier.controllerKeyId }) + if (typeof controllerKey === 'undefined') { + throw new Error('invalid_argument: identifier.controllerKeyId is not managed by this agent') + } + return new EthrDID({ + identifier: identifier.did, + provider: this.web3Provider, + chainNameOrId: this.network, + rpcUrl: this.rpcUrl, + registry: this.registry, + txSigner: new KmsEthereumSigner(controllerKey, context), + }) } async addKey( { identifier, key, options }: { identifier: IIdentifier; key: IKey; options?: any }, - context: IContext, + context: IRequiredContext, ): Promise { - const ethrDid = new EthrDID({ - identifier: identifier.did, - provider: this.getWeb3Provider(identifier, context), - registry: this.registry, - }) - + const ethrDid = await this.getEthrDidController(identifier, context) const usg = 'veriKey' const attribute = 'did/pub/' + key.type + '/' + usg + '/hex' const value = '0x' + key.publicKeyHex @@ -119,13 +115,9 @@ export class EthrDIDProvider extends AbstractIdentifierProvider { async addService( { identifier, service, options }: { identifier: IIdentifier; service: IService; options?: any }, - context: IContext, + context: IRequiredContext, ): Promise { - const ethrDid = new EthrDID({ - identifier: identifier.did, - provider: this.getWeb3Provider(identifier, context), - registry: this.registry, - }) + const ethrDid = await this.getEthrDidController(identifier, context) const attribute = 'did/svc/' + service.type const value = service.serviceEndpoint @@ -141,13 +133,9 @@ export class EthrDIDProvider extends AbstractIdentifierProvider { async removeKey( args: { identifier: IIdentifier; kid: string; options?: any }, - context: IContext, + context: IRequiredContext, ): Promise { - const ethrDid = new EthrDID({ - identifier: args.identifier.did, - provider: this.getWeb3Provider(args.identifier, context), - registry: this.registry, - }) + const ethrDid = await this.getEthrDidController(args.identifier, context) const key = args.identifier.keys.find((k) => k.kid === args.kid) if (!key) throw Error('Key not found') @@ -164,13 +152,9 @@ export class EthrDIDProvider extends AbstractIdentifierProvider { async removeService( args: { identifier: IIdentifier; id: string; options?: any }, - context: IContext, + context: IRequiredContext, ): Promise { - const ethrDid = new EthrDID({ - identifier: args.identifier.did, - provider: this.getWeb3Provider(args.identifier, context), - registry: this.registry, - }) + const ethrDid = await this.getEthrDidController(args.identifier, context) const service = args.identifier.services.find((s) => s.id === args.id) if (!service) throw Error('Service not found') diff --git a/packages/did-provider-ethr/src/kms-eth-signer.ts b/packages/did-provider-ethr/src/kms-eth-signer.ts new file mode 100644 index 000000000..28b63b68a --- /dev/null +++ b/packages/did-provider-ethr/src/kms-eth-signer.ts @@ -0,0 +1,53 @@ +import { TransactionRequest, Provider } from '@ethersproject/abstract-provider' +import { Signer } from '@ethersproject/abstract-signer' +import { getAddress } from '@ethersproject/address' +import { Bytes } from '@ethersproject/bytes' +import { Deferrable, resolveProperties } from '@ethersproject/properties' +import { computeAddress, serialize, UnsignedTransaction } from '@ethersproject/transactions' +import { IRequiredContext } from './ethr-did-provider' +import { IKey } from '@veramo/core' + +/** + * Creates an `@ethersproject/abstract-signer` implementation by wrapping + * a veramo agent with a key-manager that should be capable of `eth_signTransaction` + */ +export class KmsEthereumSigner extends Signer { + private context: IRequiredContext + private controllerKey: IKey + + constructor(controllerKey: IKey, context: IRequiredContext) { + super() + this.controllerKey = controllerKey + this.context = context + } + + async getAddress(): Promise { + return computeAddress('0x' + this.controllerKey.publicKeyHex) + } + + async signTransaction(transaction: Deferrable): Promise { + const tx = await resolveProperties(transaction) + if (tx.from != null) { + const thisAddress = await this.getAddress() + if (getAddress(tx.from) !== thisAddress) { + throw new Error(`transaction from address mismatch ${transaction.from} != ${thisAddress}`) + } + delete tx.from + } + const signature = await this.context.agent.keyManagerSign({ + kid: this.controllerKey.kid, + data: serialize(tx), + alg: 'eth_signTransaction', + enc: 'base16', + }) + return serialize(tx, signature) + } + + signMessage(message: string | Bytes): Promise { + throw new Error('not_implemented: signMessage() Method not implemented by KmsEthereumSigner.') + } + + connect(provider: Provider): Signer { + throw new Error('not_implemented: connect() Method not implemented by KmsEthereumSigner.') + } +} diff --git a/packages/kms-local/src/key-management-system.ts b/packages/kms-local/src/key-management-system.ts index 67b93a62f..cf12f4f19 100644 --- a/packages/kms-local/src/key-management-system.ts +++ b/packages/kms-local/src/key-management-system.ts @@ -119,20 +119,24 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { if (key.type === 'Ed25519' && (typeof alg === 'undefined' || ['Ed25519', 'EdDSA'].includes(alg))) { const signer = EdDSASigner(key.privateKeyHex) const signature = await signer(data) + //base64url encoded string return signature as string } else if (key.type === 'Secp256k1') { if (typeof alg === 'undefined' || ['ES256K', 'ES256K-R'].includes(alg)) { const signer = ES256KSigner(key.privateKeyHex, alg === 'ES256K-R') const signature = await signer(data) + //base64url encoded string return signature as string } else if (['eth_signTransaction', 'signTransaction', 'signTx'].includes(alg)) { const tx = parse(data) const wallet = new Wallet(key.privateKeyHex) const signature = await wallet.signTransaction(tx as any) + //HEX encoded string return signature } else if (alg === 'eth_signMessage') { const wallet = new Wallet(key.privateKeyHex) const signature = await wallet.signMessage(data) + //HEX encoded string return signature } else if (['eth_signTypedData', 'EthereumEip712Signature2021'].includes(alg)) { let msg, msgDomain, msgTypes @@ -150,6 +154,7 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { const wallet = new Wallet(key.privateKeyHex) const signature = await wallet._signTypedData(msgDomain, msgTypes, msg) + //HEX encoded string return signature } } diff --git a/yarn.lock b/yarn.lock index 7c14b31bb..7a64c8a01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3566,11 +3566,6 @@ bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -bn.js@4.11.6: - version "4.11.6" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" - integrity sha1-UzRK2xRhehP26N0s4okF0cC6MhU= - bn.js@^4.0.0, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" @@ -5209,53 +5204,6 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -ethjs-format@0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/ethjs-format/-/ethjs-format-0.1.8.tgz#925ecdd965ea72a2a2daf2a122e5bf80b5ad522a" - integrity sha1-kl7N2WXqcqKi2vKhIuW/gLWtUio= - dependencies: - bn.js "4.11.6" - ethjs-schema "0.1.4" - ethjs-util "0.1.3" - is-hex-prefixed "1.0.0" - number-to-bn "1.7.0" - strip-hex-prefix "1.0.0" - -ethjs-provider-http@0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/ethjs-provider-http/-/ethjs-provider-http-0.1.6.tgz#1ec5d9b4be257ef1d56a500b22a741985e889420" - integrity sha1-HsXZtL4lfvHValALIqdBmF6IlCA= - dependencies: - xhr2 "0.1.3" - -ethjs-provider-signer@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/ethjs-provider-signer/-/ethjs-provider-signer-0.1.4.tgz#6bd5cb38a8d5b0ddf46ac1e23a60eea1716171ae" - integrity sha1-a9XLOKjVsN30asHiOmDuoXFhca4= - dependencies: - ethjs-provider-http "0.1.6" - ethjs-rpc "0.1.2" - -ethjs-rpc@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ethjs-rpc/-/ethjs-rpc-0.1.2.tgz#39a3456b51c59aeeafb5ba556589a59f2da88d26" - integrity sha1-OaNFa1HFmu6vtbpVZYmlny2ojSY= - dependencies: - ethjs-format "0.1.8" - -ethjs-schema@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/ethjs-schema/-/ethjs-schema-0.1.4.tgz#0323a16333b1ace9a8f1d696a6ee63448fdd455f" - integrity sha1-AyOhYzOxrOmo8daWpu5jRI/dRV8= - -ethjs-util@0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.3.tgz#dfd5ea4a400dc5e421a889caf47e081ada78bb55" - integrity sha1-39XqSkANxeQhqInK9H4IGtp4u1U= - dependencies: - is-hex-prefixed "1.0.0" - strip-hex-prefix "1.0.0" - ethr-did-registry@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/ethr-did-registry/-/ethr-did-registry-0.0.3.tgz#f363d2c73cb9572b57bd7a5c9c90c88485feceb5" @@ -6688,11 +6636,6 @@ is-glob@^4.0.1: dependencies: is-extglob "^2.1.1" -is-hex-prefixed@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" - integrity sha1-fY035q135dEnFIkTxXPggtd39VQ= - is-installed-globally@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" @@ -9174,14 +9117,6 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -number-to-bn@1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" - integrity sha1-uzYjWS9+X54AMLGXe9QaDFP+HqA= - dependencies: - bn.js "4.11.6" - strip-hex-prefix "1.0.0" - nwsapi@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" @@ -11297,13 +11232,6 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-hex-prefix@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" - integrity sha1-DF8VX+8RUTczd96du1iNoFUA428= - dependencies: - is-hex-prefixed "1.0.0" - strip-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" @@ -12359,11 +12287,6 @@ xdg-basedir@^3.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= -xhr2@0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.3.tgz#cbfc4759a69b4a888e78cf4f20b051038757bd11" - integrity sha1-y/xHWaabSoiOeM9PILBRA4dXvRE= - xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" From b50bf862e0a19412caa9569ef03985c13f319b4d Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Thu, 20 May 2021 19:29:29 +0200 Subject: [PATCH 4/7] fix(did-provider-ethr): fix web3 provider and transaction data for did:ethr CRUD fixes #522 --- packages/cli/default/default.yml | 1 + packages/core/plugin.schema.json | 3 ++- packages/core/src/types/IKeyManager.ts | 2 +- .../did-provider-ethr/src/ethr-did-provider.ts | 2 +- .../did-provider-ethr/src/kms-eth-signer.ts | 8 +++++--- packages/key-manager/src/key-manager.ts | 7 ++++++- .../src/key-management-system.ts | 16 +++++++++++----- packages/kms-local/src/key-management-system.ts | 17 +++++++++-------- 8 files changed, 36 insertions(+), 20 deletions(-) diff --git a/packages/cli/default/default.yml b/packages/cli/default/default.yml index d36ed704f..43630dd5b 100644 --- a/packages/cli/default/default.yml +++ b/packages/cli/default/default.yml @@ -13,6 +13,7 @@ constants: - keyManagerImport - keyManagerEncryptJWE - keyManagerDecryptJWE + - keyManagerSign - keyManagerSignJWT - keyManagerSignEthTX - didManagerGetProviders diff --git a/packages/core/plugin.schema.json b/packages/core/plugin.schema.json index 51bce8ab5..1269b1beb 100644 --- a/packages/core/plugin.schema.json +++ b/packages/core/plugin.schema.json @@ -542,7 +542,8 @@ "enum": [ "utf-8", "base16", - "base64" + "base64", + "hex" ], "description": "If the data is a \"string\" then you can specify which encoding is used. Default is \"utf-8\"" } diff --git a/packages/core/src/types/IKeyManager.ts b/packages/core/src/types/IKeyManager.ts index 35764f839..fa5de9070 100644 --- a/packages/core/src/types/IKeyManager.ts +++ b/packages/core/src/types/IKeyManager.ts @@ -105,7 +105,7 @@ export interface IKeyManagerSignArgs { /** * If the data is a "string" then you can specify which encoding is used. Default is "utf-8" */ - enc?: 'utf-8' | 'base16' | 'base64' + enc?: 'utf-8' | 'base16' | 'base64' | 'hex' [x: string]: any } diff --git a/packages/did-provider-ethr/src/ethr-did-provider.ts b/packages/did-provider-ethr/src/ethr-did-provider.ts index 9703a0edc..6fc6a1f12 100644 --- a/packages/did-provider-ethr/src/ethr-did-provider.ts +++ b/packages/did-provider-ethr/src/ethr-did-provider.ts @@ -91,7 +91,7 @@ export class EthrDIDProvider extends AbstractIdentifierProvider { chainNameOrId: this.network, rpcUrl: this.rpcUrl, registry: this.registry, - txSigner: new KmsEthereumSigner(controllerKey, context), + txSigner: new KmsEthereumSigner(controllerKey, context, this.web3Provider), }) } diff --git a/packages/did-provider-ethr/src/kms-eth-signer.ts b/packages/did-provider-ethr/src/kms-eth-signer.ts index 28b63b68a..d975f1b1c 100644 --- a/packages/did-provider-ethr/src/kms-eth-signer.ts +++ b/packages/did-provider-ethr/src/kms-eth-signer.ts @@ -14,11 +14,13 @@ import { IKey } from '@veramo/core' export class KmsEthereumSigner extends Signer { private context: IRequiredContext private controllerKey: IKey + readonly provider?: Provider - constructor(controllerKey: IKey, context: IRequiredContext) { + constructor(controllerKey: IKey, context: IRequiredContext, provider?: Provider) { super() this.controllerKey = controllerKey this.context = context + this.provider = provider } async getAddress(): Promise { @@ -40,7 +42,7 @@ export class KmsEthereumSigner extends Signer { alg: 'eth_signTransaction', enc: 'base16', }) - return serialize(tx, signature) + return signature } signMessage(message: string | Bytes): Promise { @@ -48,6 +50,6 @@ export class KmsEthereumSigner extends Signer { } connect(provider: Provider): Signer { - throw new Error('not_implemented: connect() Method not implemented by KmsEthereumSigner.') + return new KmsEthereumSigner(this.controllerKey, this.context, provider) } } diff --git a/packages/key-manager/src/key-manager.ts b/packages/key-manager/src/key-manager.ts index c0e695688..68b99d6c7 100644 --- a/packages/key-manager/src/key-manager.ts +++ b/packages/key-manager/src/key-manager.ts @@ -121,7 +121,12 @@ export class KeyManager implements IAgentPlugin { let dataBytes const encoding = enc || 'utf-8' if (typeof data === 'string') { - dataBytes = u8a.fromString(data, encoding) + if (encoding === 'base16' || encoding === 'hex') { + const preData = data.startsWith('0x') ? data.substring(2) : data + dataBytes = u8a.fromString(preData, 'base16') + } else { + dataBytes = u8a.fromString(data, encoding) + } } else { dataBytes = data } diff --git a/packages/kms-local-react-native/src/key-management-system.ts b/packages/kms-local-react-native/src/key-management-system.ts index 7c46aeca2..ccc7d11e6 100644 --- a/packages/kms-local-react-native/src/key-management-system.ts +++ b/packages/kms-local-react-native/src/key-management-system.ts @@ -6,7 +6,7 @@ import { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer' import { TransactionRequest } from '@ethersproject/abstract-provider' import { arrayify } from '@ethersproject/bytes' import { toUtf8String } from '@ethersproject/strings' -import { serialize, parse } from '@ethersproject/transactions' +import { serialize, parse, Transaction } from '@ethersproject/transactions' import { Wallet } from '@ethersproject/wallet' import * as u8a from 'uint8arrays' import { DIDComm } from './didcomm' @@ -82,7 +82,8 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { /**@deprecated please use `sign({key, alg: 'eth_signTransaction', data: arrayify(serialize(transaction))})` instead */ async signEthTX({ key, transaction }: { key: IKey; transaction: object }): Promise { - const data = arrayify(serialize(transaction)) + const {v, r, s, type, ...tx} = transaction + const data = arrayify(serialize(tx)) const alg = 'eth_signTransaction' return this.sign({ key, data, alg }) } @@ -119,20 +120,24 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { if (key.type === 'Ed25519' && (typeof alg === 'undefined' || ['Ed25519', 'EdDSA'].includes(alg))) { const signer = EdDSASigner(key.privateKeyHex) const signature = await signer(data) + //base64url encoded string return signature as string } else if (key.type === 'Secp256k1') { if (typeof alg === 'undefined' || ['ES256K', 'ES256K-R'].includes(alg)) { const signer = ES256KSigner(key.privateKeyHex, alg === 'ES256K-R') const signature = await signer(data) + //base64url encoded string return signature as string } else if (['eth_signTransaction', 'signTransaction', 'signTx'].includes(alg)) { - const tx = parse(data) + const {v, r, s, type, ...tx} = parse(data) const wallet = new Wallet(key.privateKeyHex) - const signature = await wallet.signTransaction(tx as any) - return signature + const signedRawTransaction = await wallet.signTransaction(tx) + //HEX encoded string, 0x prefixed + return signedRawTransaction } else if (alg === 'eth_signMessage') { const wallet = new Wallet(key.privateKeyHex) const signature = await wallet.signMessage(data) + //HEX encoded string, 0x prefixed return signature } else if (['eth_signTypedData', 'EthereumEip712Signature2021'].includes(alg)) { let msg, msgDomain, msgTypes @@ -150,6 +155,7 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { const wallet = new Wallet(key.privateKeyHex) const signature = await wallet._signTypedData(msgDomain, msgTypes, msg) + //HEX encoded string return signature } } diff --git a/packages/kms-local/src/key-management-system.ts b/packages/kms-local/src/key-management-system.ts index cf12f4f19..c563a9ac7 100644 --- a/packages/kms-local/src/key-management-system.ts +++ b/packages/kms-local/src/key-management-system.ts @@ -6,7 +6,7 @@ import { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer' import { TransactionRequest } from '@ethersproject/abstract-provider' import { arrayify } from '@ethersproject/bytes' import { toUtf8String } from '@ethersproject/strings' -import { serialize, parse } from '@ethersproject/transactions' +import { serialize, parse, Transaction } from '@ethersproject/transactions' import { Wallet } from '@ethersproject/wallet' import * as u8a from 'uint8arrays' import { DIDComm } from './didcomm' @@ -82,12 +82,13 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { /**@deprecated please use `sign({key, alg: 'eth_signTransaction', data: arrayify(serialize(transaction))})` instead */ async signEthTX({ key, transaction }: { key: IKey; transaction: object }): Promise { - const data = arrayify(serialize(transaction)) + const {v, r, s, type, ...tx} = transaction + const data = arrayify(serialize(tx)) const alg = 'eth_signTransaction' return this.sign({ key, data, alg }) } - /**@deprecated please use sign() instead */ + /**@deprecated please use `sign({key, data})` instead, with `Uint8Array` data */ async signJWT({ key, data }: { key: IKey; data: string | Uint8Array }): Promise { let dataBytes: Uint8Array if (typeof data === 'string') { @@ -128,15 +129,15 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { //base64url encoded string return signature as string } else if (['eth_signTransaction', 'signTransaction', 'signTx'].includes(alg)) { - const tx = parse(data) + const {v, r, s, type, ...tx} = parse(data) const wallet = new Wallet(key.privateKeyHex) - const signature = await wallet.signTransaction(tx as any) - //HEX encoded string - return signature + const signedRawTransaction = await wallet.signTransaction(tx) + //HEX encoded string, 0x prefixed + return signedRawTransaction } else if (alg === 'eth_signMessage') { const wallet = new Wallet(key.privateKeyHex) const signature = await wallet.signMessage(data) - //HEX encoded string + //HEX encoded string, 0x prefixed return signature } else if (['eth_signTypedData', 'EthereumEip712Signature2021'].includes(alg)) { let msg, msgDomain, msgTypes From e988cb3c9d25101dd75e2089d80750a205cb03bf Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 25 May 2021 12:03:23 +0200 Subject: [PATCH 5/7] feat(key-manager): add default implementation to legacy sign methods that leverage the unified signer --- .vscode/settings.json | 1 + .../src/abstract-key-management-system.ts | 28 ++++++++++++++++-- .../src/key-management-system.ts | 29 ++----------------- .../kms-local/src/key-management-system.ts | 27 +---------------- 4 files changed, 30 insertions(+), 55 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5bb39d90d..b8b47cbab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "typescript.tsdk": "node_modules/typescript/lib", "cSpell.words": [ + "arrayify", "ethersproject" ] } \ No newline at end of file diff --git a/packages/key-manager/src/abstract-key-management-system.ts b/packages/key-manager/src/abstract-key-management-system.ts index defe16b90..ee38fd9b5 100644 --- a/packages/key-manager/src/abstract-key-management-system.ts +++ b/packages/key-manager/src/abstract-key-management-system.ts @@ -1,12 +1,36 @@ import { IKey, TKeyType } from '@veramo/core' +import { arrayify } from '@ethersproject/bytes' +import { serialize, Transaction } from '@ethersproject/transactions' +import * as u8a from 'uint8arrays' export abstract class AbstractKeyManagementSystem { abstract createKey(args: { type: TKeyType; meta?: any }): Promise> abstract deleteKey(args: { kid: string }): Promise abstract encryptJWE(args: { key: IKey; to: Omit; data: string }): Promise abstract decryptJWE(args: { key: IKey; data: string }): Promise - abstract signJWT(args: { key: IKey; data: string | Uint8Array }): Promise - abstract signEthTX(args: { key: IKey; transaction: object }): Promise + + /**@deprecated please use `sign({key, alg: 'eth_signTransaction', data: arrayify(serialize(transaction))})` instead */ + async signEthTX({ key, transaction }: { key: IKey; transaction: object }): Promise { + const { v, r, s, type, ...tx } = transaction + const data = arrayify(serialize(tx)) + const alg = 'eth_signTransaction' + return this.sign({ key, data, alg }) + } + + /**@deprecated please use `sign({key, data})` instead, with `Uint8Array` data */ + async signJWT({ key, data }: { key: IKey; data: string | Uint8Array }): Promise { + let dataBytes: Uint8Array + if (typeof data === 'string') { + try { + dataBytes = arrayify(data, { allowMissingPrefix: true }) + } catch (e) { + dataBytes = u8a.fromString(data, 'utf-8') + } + } else { + dataBytes = data + } + return this.sign({ key, data: dataBytes }) + } abstract sign(args: { key: IKey; alg?: string; data: Uint8Array; [x: string]: any }): Promise } diff --git a/packages/kms-local-react-native/src/key-management-system.ts b/packages/kms-local-react-native/src/key-management-system.ts index ccc7d11e6..d28a47f36 100644 --- a/packages/kms-local-react-native/src/key-management-system.ts +++ b/packages/kms-local-react-native/src/key-management-system.ts @@ -4,11 +4,9 @@ import sodium from 'libsodium-wrappers' import { EdDSASigner, ES256KSigner } from 'did-jwt' import { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer' import { TransactionRequest } from '@ethersproject/abstract-provider' -import { arrayify } from '@ethersproject/bytes' import { toUtf8String } from '@ethersproject/strings' -import { serialize, parse, Transaction } from '@ethersproject/transactions' +import { parse } from '@ethersproject/transactions' import { Wallet } from '@ethersproject/wallet' -import * as u8a from 'uint8arrays' import { DIDComm } from './didcomm' const didcomm = new DIDComm() import Debug from 'debug' @@ -80,29 +78,6 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { return unpackMessage.message } - /**@deprecated please use `sign({key, alg: 'eth_signTransaction', data: arrayify(serialize(transaction))})` instead */ - async signEthTX({ key, transaction }: { key: IKey; transaction: object }): Promise { - const {v, r, s, type, ...tx} = transaction - const data = arrayify(serialize(tx)) - const alg = 'eth_signTransaction' - return this.sign({ key, data, alg }) - } - - /**@deprecated please use `sign({key, data})` instead, with `Uint8Array` data */ - async signJWT({ key, data }: { key: IKey; data: string | Uint8Array }): Promise { - let dataBytes: Uint8Array - if (typeof data === 'string') { - try { - dataBytes = arrayify(data, { allowMissingPrefix: true }) - } catch (e) { - dataBytes = u8a.fromString(data, 'utf-8') - } - } else { - dataBytes = data - } - return this.sign({ key, data: dataBytes }) - } - async sign({ key, alg, @@ -129,7 +104,7 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { //base64url encoded string return signature as string } else if (['eth_signTransaction', 'signTransaction', 'signTx'].includes(alg)) { - const {v, r, s, type, ...tx} = parse(data) + const { v, r, s, type, ...tx } = parse(data) const wallet = new Wallet(key.privateKeyHex) const signedRawTransaction = await wallet.signTransaction(tx) //HEX encoded string, 0x prefixed diff --git a/packages/kms-local/src/key-management-system.ts b/packages/kms-local/src/key-management-system.ts index c563a9ac7..38ec0da8c 100644 --- a/packages/kms-local/src/key-management-system.ts +++ b/packages/kms-local/src/key-management-system.ts @@ -4,11 +4,9 @@ import sodium from 'libsodium-wrappers' import { EdDSASigner, ES256KSigner } from 'did-jwt' import { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer' import { TransactionRequest } from '@ethersproject/abstract-provider' -import { arrayify } from '@ethersproject/bytes' import { toUtf8String } from '@ethersproject/strings' -import { serialize, parse, Transaction } from '@ethersproject/transactions' +import { parse } from '@ethersproject/transactions' import { Wallet } from '@ethersproject/wallet' -import * as u8a from 'uint8arrays' import { DIDComm } from './didcomm' const didcomm = new DIDComm() import Debug from 'debug' @@ -80,29 +78,6 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { return unpackMessage.message } - /**@deprecated please use `sign({key, alg: 'eth_signTransaction', data: arrayify(serialize(transaction))})` instead */ - async signEthTX({ key, transaction }: { key: IKey; transaction: object }): Promise { - const {v, r, s, type, ...tx} = transaction - const data = arrayify(serialize(tx)) - const alg = 'eth_signTransaction' - return this.sign({ key, data, alg }) - } - - /**@deprecated please use `sign({key, data})` instead, with `Uint8Array` data */ - async signJWT({ key, data }: { key: IKey; data: string | Uint8Array }): Promise { - let dataBytes: Uint8Array - if (typeof data === 'string') { - try { - dataBytes = arrayify(data, { allowMissingPrefix: true }) - } catch (e) { - dataBytes = u8a.fromString(data, 'utf-8') - } - } else { - dataBytes = data - } - return this.sign({ key, data: dataBytes }) - } - async sign({ key, alg, From 3abf915ff24f1537f4bfad22c36c7d929b0460aa Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Wed, 26 May 2021 18:51:01 +0200 Subject: [PATCH 6/7] test(key-manager): add extra tests using testvectors for `keyManagerSign()` --- __tests__/shared/keyManager.ts | 49 +++++++++++++++++++++++++++++- __tests__/shared/verifiableData.ts | 47 ++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/__tests__/shared/keyManager.ts b/__tests__/shared/keyManager.ts index f6eb8b261..d82dc91ba 100644 --- a/__tests__/shared/keyManager.ts +++ b/__tests__/shared/keyManager.ts @@ -1,4 +1,6 @@ -import { TAgent, IDIDManager, IKeyManager, IAgentOptions, TKeyType } from '../../packages/core/src' +import { TKeyType } from '@veramo/core' +import { TAgent, IDIDManager, IKeyManager, IAgentOptions } from '../../packages/core/src' +import { ICredentialIssuer } from '@veramo/credential-w3c/src' import { serialize } from '@ethersproject/transactions' type ConfiguredAgent = TAgent @@ -260,6 +262,39 @@ export default (testContext: { '0xf869018504a817c800840291882094ce31a19193d4b23f4e9d6163d7247243baf801c3830493e0801ba0f16e2206290181c3feaa04051dad19089105c24339dbdf0d80147b48a59fa152a0770e8751ec77ccc78e8b207023f168444f7cfb67055c55c70ef75234458a3d51', ) }) + + it('should sign JWT using generic signer', async () => { + const signature = await agent.keyManagerSign({ + kid: importedKey.kid, + data: 'bla.bla', + alg: 'ES256K', + enc: 'utf-8', + }) + expect(signature).toEqual( + 'pNAFkgmuKhqMbb_6Km--ZmY7UCkWunWUuNajSfF6rv5lEa5nNXCU7cnZBZVptU7u8h150qetqkqUaahAf-Cepw', + ) + }) + + it('should sign EthTX using generic signer', async () => { + const txData = serialize({ + to: '0xce31a19193d4b23f4e9d6163d7247243bAF801c3', + value: 300000, + gasLimit: 43092000, + gasPrice: 20000000000, + nonce: 1, + }) + + const rawTx = await agent.keyManagerSign({ + alg: 'eth_signTransaction', + data: txData, + enc: 'hex', + kid: importedKey.kid, + }) + + expect(rawTx).toEqual( + '0xf869018504a817c800840291882094ce31a19193d4b23f4e9d6163d7247243baf801c3830493e0801ba0f16e2206290181c3feaa04051dad19089105c24339dbdf0d80147b48a59fa152a0770e8751ec77ccc78e8b207023f168444f7cfb67055c55c70ef75234458a3d51', + ) + }) }) describe('using Ed25519 testvectors', () => { @@ -285,6 +320,18 @@ export default (testContext: { '_2P0iukN2CPH1nQ6LeBm1zQHHp3U4wSYDrpeWTWkp7yuzJex6O60Z4OhdfD5I9WPHV734US8n5vyD2VDbT1UCg', ) }) + + it('should sign JWT using generic signer', async () => { + const signature = await agent.keyManagerSign({ + kid: importedKey.kid, + data: 'bla.bla', + alg: 'EdDSA', + enc: 'utf-8', + }) + expect(signature).toEqual( + '_2P0iukN2CPH1nQ6LeBm1zQHHp3U4wSYDrpeWTWkp7yuzJex6O60Z4OhdfD5I9WPHV734US8n5vyD2VDbT1UCg', + ) + }) }) }) } diff --git a/__tests__/shared/verifiableData.ts b/__tests__/shared/verifiableData.ts index 144a2d1c8..4caa099c2 100644 --- a/__tests__/shared/verifiableData.ts +++ b/__tests__/shared/verifiableData.ts @@ -2,6 +2,7 @@ import { TAgent, IDIDManager, IIdentifier, IDataStore } from '../../packages/cor import { IDataStoreORM } from '../../packages/data-store/src' import { ICredentialIssuer } from '../../packages/credential-w3c/src' import { decodeJWT } from 'did-jwt' +import { TKeyType } from '@veramo/core' type ConfiguredAgent = TAgent @@ -239,5 +240,51 @@ export default (testContext: { }), ).rejects.toThrow('Verifiable presentation not found') }) + + describe('using testvectors', () => { + const importedDID = { + did: 'did:ethr:rinkeby:0x03155ee0cbefeecd80de63a62b4ed8f0f97ac22a58f76a265903b9acab79bf018c', + provider: 'did:ethr:rinkeby', + controllerKeyId: + '04155ee0cbefeecd80de63a62b4ed8f0f97ac22a58f76a265903b9acab79bf018c7037e2bd897812170c92a4c978d6a10481491a37299d74c4bd412a111a4ac875', + keys: [ + { + kid: + '04155ee0cbefeecd80de63a62b4ed8f0f97ac22a58f76a265903b9acab79bf018c7037e2bd897812170c92a4c978d6a10481491a37299d74c4bd412a111a4ac875', + kms: 'local', + type: 'Secp256k1', + publicKeyHex: + '04155ee0cbefeecd80de63a62b4ed8f0f97ac22a58f76a265903b9acab79bf018c7037e2bd897812170c92a4c978d6a10481491a37299d74c4bd412a111a4ac875', + privateKeyHex: '31d1ec15ff8110442012fef0d1af918c0e09b2e2ab821bba52ecc85f8655ec63', + }, + ], + services: [], + } + + beforeAll(async () => { + const imported = await agent.didManagerImport(importedDID) + }) + + it('signs JWT with ES256K', async () => { + const credentialInput = { + credentialSubject: { id: 'did:example:subject', name: 'Alice' }, + issuer: { id: importedDID.did }, + } + const { proof, issuanceDate, ...comparableOutput } = await agent.createVerifiableCredential({ + credential: credentialInput, + proofFormat: 'jwt', + save: false, + removeOriginalFields: true, + }) + expect(comparableOutput).toEqual({ + credentialSubject: { name: 'Alice', id: 'did:example:subject' }, + issuer: { + id: 'did:ethr:rinkeby:0x03155ee0cbefeecd80de63a62b4ed8f0f97ac22a58f76a265903b9acab79bf018c', + }, + type: ['VerifiableCredential'], + '@context': ['https://www.w3.org/2018/credentials/v1'], + }) + }) + }) }) } From b49b57cba7a72b88e6b3e7323864e14d67e41585 Mon Sep 17 00:00:00 2001 From: Mircea Nistor Date: Tue, 1 Jun 2021 12:03:38 +0200 Subject: [PATCH 7/7] chore(kms-local): rename parameters of `sign()` method & separate signing code into methods --- __tests__/shared/keyManager.ts | 18 +-- packages/core/plugin.schema.json | 76 ++++----- packages/core/src/types/IKeyManager.ts | 10 +- .../src/__tests__/action-handler.test.ts | 54 +++---- packages/credential-w3c/src/action-handler.ts | 15 +- packages/data-store/plugin.schema.json | 4 +- packages/did-comm/plugin.schema.json | 6 +- .../did-provider-ethr/src/kms-eth-signer.ts | 6 +- .../src/abstract-key-management-system.ts | 7 +- packages/key-manager/src/key-manager.ts | 9 +- .../src/key-management-system.ts | 149 +++++++++++------- .../kms-local/src/key-management-system.ts | 149 +++++++++++------- .../selective-disclosure/plugin.schema.json | 12 +- .../src/action-handler.ts | 8 +- yarn.lock | 5 + 15 files changed, 304 insertions(+), 224 deletions(-) diff --git a/__tests__/shared/keyManager.ts b/__tests__/shared/keyManager.ts index d82dc91ba..7c60d6266 100644 --- a/__tests__/shared/keyManager.ts +++ b/__tests__/shared/keyManager.ts @@ -265,10 +265,10 @@ export default (testContext: { it('should sign JWT using generic signer', async () => { const signature = await agent.keyManagerSign({ - kid: importedKey.kid, + algorithm: 'ES256K', data: 'bla.bla', - alg: 'ES256K', - enc: 'utf-8', + encoding: 'utf-8', + keyRef: importedKey.kid, }) expect(signature).toEqual( 'pNAFkgmuKhqMbb_6Km--ZmY7UCkWunWUuNajSfF6rv5lEa5nNXCU7cnZBZVptU7u8h150qetqkqUaahAf-Cepw', @@ -285,10 +285,10 @@ export default (testContext: { }) const rawTx = await agent.keyManagerSign({ - alg: 'eth_signTransaction', + algorithm: 'eth_signTransaction', data: txData, - enc: 'hex', - kid: importedKey.kid, + encoding: 'hex', + keyRef: importedKey.kid, }) expect(rawTx).toEqual( @@ -323,10 +323,10 @@ export default (testContext: { it('should sign JWT using generic signer', async () => { const signature = await agent.keyManagerSign({ - kid: importedKey.kid, + keyRef: importedKey.kid, data: 'bla.bla', - alg: 'EdDSA', - enc: 'utf-8', + algorithm: 'EdDSA', + encoding: 'utf-8', }) expect(signature).toEqual( '_2P0iukN2CPH1nQ6LeBm1zQHHp3U4wSYDrpeWTWkp7yuzJex6O60Z4OhdfD5I9WPHV734US8n5vyD2VDbT1UCg', diff --git a/packages/core/plugin.schema.json b/packages/core/plugin.schema.json index 1269b1beb..26c1d8c17 100644 --- a/packages/core/plugin.schema.json +++ b/packages/core/plugin.schema.json @@ -17,7 +17,7 @@ "required": [ "didUrl" ], - "description": "Input arguments for {@link IResolver.resolveDid | resolveDid}" + "description": "Input arguments for {@link IResolver.resolveDid | resolveDid }" }, "DIDResolutionOptions": { "type": "object", @@ -357,7 +357,7 @@ "type", "kms" ], - "description": "Input arguments for {@link IKeyManager.keyManagerCreate | keyManagerCreate}" + "description": "Input arguments for {@link IKeyManager.keyManagerCreate | keyManagerCreate }" }, "TKeyType": { "type": "string", @@ -437,7 +437,7 @@ "kid", "data" ], - "description": "Input arguments for {@link IKeyManager.keyManagerDecryptJWE | keyManagerDecryptJWE}" + "description": "Input arguments for {@link IKeyManager.keyManagerDecryptJWE | keyManagerDecryptJWE }" }, "IKeyManagerDeleteArgs": { "type": "object", @@ -450,7 +450,7 @@ "required": [ "kid" ], - "description": "Input arguments for {@link IKeyManager.keyManagerDelete | keyManagerDelete}" + "description": "Input arguments for {@link IKeyManager.keyManagerDelete | keyManagerDelete }" }, "IKeyManagerEncryptJWEArgs": { "type": "object", @@ -507,7 +507,7 @@ "to", "data" ], - "description": "Input arguments for {@link IKeyManager.keyManagerEncryptJWE | keyManagerEncryptJWE}" + "description": "Input arguments for {@link IKeyManager.keyManagerEncryptJWE | keyManagerEncryptJWE }" }, "IKeyManagerGetArgs": { "type": "object", @@ -520,24 +520,24 @@ "required": [ "kid" ], - "description": "Input arguments for {@link IKeyManager.keyManagerGet | keyManagerGet}" + "description": "Input arguments for {@link IKeyManager.keyManagerGet | keyManagerGet }" }, "IKeyManagerSignArgs": { "type": "object", "properties": { - "kid": { + "keyRef": { "type": "string", - "description": "Key ID" + "description": "The key handle, as returned during `keyManagerCreateKey`" }, - "alg": { + "algorithm": { "type": "string", - "description": "The algorithm to use for signing. This must be one of the algorithms supported by the KMS for this key type." + "description": "The algorithm to use for signing. This must be one of the algorithms supported by the KMS for this key type.\n\nThe algorithm used here should match one of the names listed in `IKey.meta.algorithms`" }, "data": { "type": "string", "description": "Data to sign" }, - "enc": { + "encoding": { "type": "string", "enum": [ "utf-8", @@ -549,10 +549,10 @@ } }, "required": [ - "kid", + "keyRef", "data" ], - "description": "Input arguments for {@link IKeyManager.keyManagerSign | keyManagerSign}" + "description": "Input arguments for {@link IKeyManager.keyManagerSign | keyManagerSign }" }, "IKeyManagerSignEthTXArgs": { "type": "object", @@ -570,7 +570,7 @@ "kid", "transaction" ], - "description": "Input arguments for {@link IKeyManager.keyManagerSignEthTX | keyManagerSignEthTX}" + "description": "Input arguments for {@link IKeyManager.keyManagerSignEthTX | keyManagerSignEthTX }" }, "IKeyManagerSignJWTArgs": { "type": "object", @@ -635,7 +635,7 @@ "kid", "data" ], - "description": "Input arguments for {@link IKeyManager.keyManagerSignJWT | keyManagerSignJWT}" + "description": "Input arguments for {@link IKeyManager.keyManagerSignJWT | keyManagerSignJWT }" } }, "methods": { @@ -758,7 +758,7 @@ "did", "key" ], - "description": "Input arguments for {@link IDIDManager.didManagerAddKey | didManagerAddKey}" + "description": "Input arguments for {@link IDIDManager.didManagerAddKey | didManagerAddKey }" }, "IKey": { "type": "object", @@ -842,7 +842,7 @@ "did", "service" ], - "description": "Input arguments for {@link IDIDManager.didManagerAddService | didManagerAddService}" + "description": "Input arguments for {@link IDIDManager.didManagerAddService | didManagerAddService }" }, "IService": { "type": "object", @@ -891,7 +891,7 @@ "description": "Optional. Identifier provider specific options" } }, - "description": "Input arguments for {@link IDIDManager.didManagerCreate | didManagerCreate}" + "description": "Input arguments for {@link IDIDManager.didManagerCreate | didManagerCreate }" }, "IIdentifier": { "type": "object", @@ -946,7 +946,7 @@ "required": [ "did" ], - "description": "Input arguments for {@link IDIDManager.didManagerDelete | didManagerDelete}" + "description": "Input arguments for {@link IDIDManager.didManagerDelete | didManagerDelete }" }, "IDIDManagerFindArgs": { "type": "object", @@ -960,7 +960,7 @@ "description": "Optional. Provider" } }, - "description": "Input arguments for {@link IDIDManager.didManagerFind | didManagerFind}" + "description": "Input arguments for {@link IDIDManager.didManagerFind | didManagerFind }" }, "IDIDManagerGetArgs": { "type": "object", @@ -973,7 +973,7 @@ "required": [ "did" ], - "description": "Input arguments for {@link IDIDManager.didManagerGet | didManagerGet}" + "description": "Input arguments for {@link IDIDManager.didManagerGet | didManagerGet }" }, "IDIDManagerGetByAliasArgs": { "type": "object", @@ -990,7 +990,7 @@ "required": [ "alias" ], - "description": "Input arguments for {@link IDIDManager.didManagerGetByAlias | didManagerGetByAlias}" + "description": "Input arguments for {@link IDIDManager.didManagerGetByAlias | didManagerGetByAlias }" }, "IDIDManagerGetOrCreateArgs": { "type": "object", @@ -1015,7 +1015,7 @@ "required": [ "alias" ], - "description": "Input arguments for {@link IDIDManager.didManagerGetOrCreate | didManagerGetOrCreate}" + "description": "Input arguments for {@link IDIDManager.didManagerGetOrCreate | didManagerGetOrCreate }" }, "IDIDManagerRemoveKeyArgs": { "type": "object", @@ -1037,7 +1037,7 @@ "did", "kid" ], - "description": "Input arguments for {@link IDIDManager.didManagerRemoveKey | didManagerRemoveKey}" + "description": "Input arguments for {@link IDIDManager.didManagerRemoveKey | didManagerRemoveKey }" }, "IDIDManagerRemoveServiceArgs": { "type": "object", @@ -1059,7 +1059,7 @@ "did", "id" ], - "description": "Input arguments for {@link IDIDManager.didManagerRemoveService | didManagerRemoveService}" + "description": "Input arguments for {@link IDIDManager.didManagerRemoveService | didManagerRemoveService }" }, "IDIDManagerSetAliasArgs": { "type": "object", @@ -1077,7 +1077,7 @@ "did", "alias" ], - "description": "Input arguments for {@link IDIDManager.didManagerSetAlias | didManagerSetAlias}" + "description": "Input arguments for {@link IDIDManager.didManagerSetAlias | didManagerSetAlias }" } }, "methods": { @@ -1221,7 +1221,7 @@ "required": [ "id" ], - "description": "Input arguments for {@link IDataStore.dataStoreGetMessage | dataStoreGetMessage}" + "description": "Input arguments for {@link IDataStore.dataStoreGetMessage | dataStoreGetMessage }" }, "IMessage": { "type": "object", @@ -1407,7 +1407,7 @@ "credentialSubject", "proof" ], - "description": "Verifiable Credential {@link https://github.com/decentralized-identifier/did-jwt-vc}" + "description": "Verifiable Credential {@link https://github.com/decentralized-identifier/did-jwt-vc }" }, "VerifiablePresentation": { "type": "object", @@ -1465,7 +1465,7 @@ "verifiableCredential", "proof" ], - "description": "Verifiable Presentation {@link https://github.com/decentralized-identifier/did-jwt-vc}" + "description": "Verifiable Presentation {@link https://github.com/decentralized-identifier/did-jwt-vc }" }, "IDataStoreGetVerifiableCredentialArgs": { "type": "object", @@ -1478,7 +1478,7 @@ "required": [ "hash" ], - "description": "Input arguments for {@link IDataStore.dataStoreGetVerifiableCredential | dataStoreGetVerifiableCredential}" + "description": "Input arguments for {@link IDataStore.dataStoreGetVerifiableCredential | dataStoreGetVerifiableCredential }" }, "IDataStoreGetVerifiablePresentationArgs": { "type": "object", @@ -1491,7 +1491,7 @@ "required": [ "hash" ], - "description": "Input arguments for {@link IDataStore.dataStoreGetVerifiablePresentation | dataStoreGetVerifiablePresentation}" + "description": "Input arguments for {@link IDataStore.dataStoreGetVerifiablePresentation | dataStoreGetVerifiablePresentation }" }, "IDataStoreSaveMessageArgs": { "type": "object", @@ -1504,7 +1504,7 @@ "required": [ "message" ], - "description": "Input arguments for {@link IDataStore.dataStoreSaveMessage | dataStoreSaveMessage}" + "description": "Input arguments for {@link IDataStore.dataStoreSaveMessage | dataStoreSaveMessage }" }, "IDataStoreSaveVerifiableCredentialArgs": { "type": "object", @@ -1517,7 +1517,7 @@ "required": [ "verifiableCredential" ], - "description": "Input arguments for {@link IDataStore.dataStoreSaveVerifiableCredential | dataStoreSaveVerifiableCredential}" + "description": "Input arguments for {@link IDataStore.dataStoreSaveVerifiableCredential | dataStoreSaveVerifiableCredential }" }, "IDataStoreSaveVerifiablePresentationArgs": { "type": "object", @@ -1530,7 +1530,7 @@ "required": [ "verifiablePresentation" ], - "description": "Input arguments for {@link IDataStore.dataStoreSaveVerifiablePresentation | dataStoreSaveVerifiablePresentation}" + "description": "Input arguments for {@link IDataStore.dataStoreSaveVerifiablePresentation | dataStoreSaveVerifiablePresentation }" } }, "methods": { @@ -1610,13 +1610,13 @@ }, "save": { "type": "boolean", - "description": "Optional. If set to `true`, the message will be saved using {@link IDataStore.dataStoreSaveMessage | dataStoreSaveMessage}" + "description": "Optional. If set to `true`, the message will be saved using {@link IDataStore.dataStoreSaveMessage | dataStoreSaveMessage }" } }, "required": [ "raw" ], - "description": "Input arguments for {@link IMessageHandler.handleMessage | handleMessage}" + "description": "Input arguments for {@link IMessageHandler.handleMessage | handleMessage }" }, "IMetaData": { "type": "object", @@ -1802,7 +1802,7 @@ "credentialSubject", "proof" ], - "description": "Verifiable Credential {@link https://github.com/decentralized-identifier/did-jwt-vc}" + "description": "Verifiable Credential {@link https://github.com/decentralized-identifier/did-jwt-vc }" }, "VerifiablePresentation": { "type": "object", @@ -1860,7 +1860,7 @@ "verifiableCredential", "proof" ], - "description": "Verifiable Presentation {@link https://github.com/decentralized-identifier/did-jwt-vc}" + "description": "Verifiable Presentation {@link https://github.com/decentralized-identifier/did-jwt-vc }" } }, "methods": { diff --git a/packages/core/src/types/IKeyManager.ts b/packages/core/src/types/IKeyManager.ts index fa5de9070..c6ce78a53 100644 --- a/packages/core/src/types/IKeyManager.ts +++ b/packages/core/src/types/IKeyManager.ts @@ -87,15 +87,17 @@ export interface IKeyManagerDecryptJWEArgs { */ export interface IKeyManagerSignArgs { /** - * Key ID + * The key handle, as returned during `keyManagerCreateKey` */ - kid: string + keyRef: string /** * The algorithm to use for signing. * This must be one of the algorithms supported by the KMS for this key type. + * + * The algorithm used here should match one of the names listed in `IKey.meta.algorithms` */ - alg?: string + algorithm?: string /** * Data to sign @@ -105,7 +107,7 @@ export interface IKeyManagerSignArgs { /** * If the data is a "string" then you can specify which encoding is used. Default is "utf-8" */ - enc?: 'utf-8' | 'base16' | 'base64' | 'hex' + encoding?: 'utf-8' | 'base16' | 'base64' | 'hex' [x: string]: any } diff --git a/packages/credential-w3c/src/__tests__/action-handler.test.ts b/packages/credential-w3c/src/__tests__/action-handler.test.ts index f7fd9774c..9c8d50958 100644 --- a/packages/credential-w3c/src/__tests__/action-handler.test.ts +++ b/packages/credential-w3c/src/__tests__/action-handler.test.ts @@ -1,10 +1,4 @@ -import { - W3CCredential, - VerifiableCredential, - IIdentifier, - W3CPresentation, - VerifiablePresentation, -} from '@veramo/core' +import { W3CCredential, VerifiableCredential, IIdentifier, W3CPresentation } from '@veramo/core' const mockDidJwtVc = { createVerifiableCredentialJwt: jest.fn().mockReturnValue('mockVcJwt'), @@ -28,10 +22,10 @@ const mockIdentifiers: IIdentifier[] = [ kid: 'kid1', publicKeyHex: 'pub', type: 'Secp256k1', - kms: 'mock' - } + kms: 'mock', + }, ], - services: [] + services: [], }, { did: 'did:example:222', @@ -42,11 +36,11 @@ const mockIdentifiers: IIdentifier[] = [ kid: 'kid2', publicKeyHex: 'pub', type: 'Secp256k1', - kms: 'mock' - } + kms: 'mock', + }, ], - services: [] - }, + services: [], + }, { did: 'did:example:333', provider: 'mock', @@ -56,11 +50,11 @@ const mockIdentifiers: IIdentifier[] = [ kid: 'kid3', publicKeyHex: 'pub', type: 'Ed25519', - kms: 'mock' - } + kms: 'mock', + }, ], - services: [] - } + services: [], + }, ] const w3c = new CredentialIssuer() @@ -70,23 +64,21 @@ let agent = { availableMethods: jest.fn(), resolveDid: jest.fn(), emit: jest.fn(), - keyManagerSignJWT: jest.fn().mockImplementation(async (args): Promise => 'mockJWT'), + keyManagerSign: jest.fn().mockImplementation(async (args): Promise => 'mockJWT'), dataStoreSaveVerifiableCredential: jest.fn().mockImplementation(async (args): Promise => true), dataStoreSaveVerifiablePresentation: jest.fn().mockImplementation(async (args): Promise => true), getSchema: jest.fn(), - didManagerGet: jest.fn() + didManagerGet: jest.fn(), } describe('@veramo/credential-w3c', () => { - test.each(mockIdentifiers)('handles createVerifiableCredential', async (mockIdentifier) => { - expect.assertions(3*mockIdentifiers.length) - + expect.assertions(3 * mockIdentifiers.length) + agent.didManagerGet = jest.fn().mockImplementation(async (args): Promise => mockIdentifier) const context: IContext = { agent: agent } - - for (let otherMockIdentifier of mockIdentifiers) { + for (let otherMockIdentifier of mockIdentifiers) { const credential: W3CCredential = { '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2020/demo/4342323'], type: ['VerifiableCredential', 'PublicProfile'], @@ -117,13 +109,13 @@ describe('@veramo/credential-w3c', () => { expect(context.agent.dataStoreSaveVerifiableCredential).toBeCalledWith({ verifiableCredential: 'mockCredential', }) - expect(vc).toEqual('mockCredential') + expect(vc).toEqual('mockCredential') } }) test.each(mockIdentifiers)('handles createVerifiablePresentation', async (mockIdentifier) => { - expect.assertions(3*mockIdentifiers.length) - + expect.assertions(3 * mockIdentifiers.length) + agent.didManagerGet = jest.fn().mockImplementation(async (args): Promise => mockIdentifier) const context: IContext = { agent: agent } @@ -147,7 +139,7 @@ describe('@veramo/credential-w3c', () => { jwt: 'mockJWT', }, } - + const presentation: W3CPresentation = { '@context': ['https://www.w3.org/2018/credentials/v1'], type: ['VerifiablePresentation'], @@ -156,7 +148,7 @@ describe('@veramo/credential-w3c', () => { issuanceDate: new Date().toISOString(), verifiableCredential: [credential], } - + const vp = await w3c.createVerifiablePresentation( { presentation, @@ -165,7 +157,7 @@ describe('@veramo/credential-w3c', () => { }, context, ) - + expect(context.agent.didManagerGet).toBeCalledWith({ did: mockIdentifier.did }) expect(context.agent.dataStoreSaveVerifiablePresentation).toBeCalledWith({ verifiablePresentation: 'mockPresentation', diff --git a/packages/credential-w3c/src/action-handler.ts b/packages/credential-w3c/src/action-handler.ts index 075db1143..a36da88f8 100644 --- a/packages/credential-w3c/src/action-handler.ts +++ b/packages/credential-w3c/src/action-handler.ts @@ -10,6 +10,7 @@ import { VerifiableCredential, VerifiablePresentation, IDataStore, + IKey, } from '@veramo/core' import { @@ -186,7 +187,7 @@ export type IContext = IAgentContext< IResolver & Pick & Pick & - Pick + Pick > /** @@ -225,12 +226,12 @@ export class CredentialIssuer implements IAgentPlugin { const key = identifier.keys.find((k) => k.type === 'Secp256k1' || k.type === 'Ed25519') if (!key) throw Error('No signing key for ' + identifier.did) //FIXME: Throw an `unsupported_format` error if the `args.proofFormat` is not `jwt` - const signer = (data: string | Uint8Array) => context.agent.keyManagerSign({ kid: key.kid, data }) debug('Signing VP with', identifier.did) let alg = 'ES256K' if (key.type === 'Ed25519') { alg = 'EdDSA' } + const signer = wrapSigner(context, key, alg) const jwt = await createVerifiablePresentationJwt( presentation, @@ -270,13 +271,12 @@ export class CredentialIssuer implements IAgentPlugin { const key = identifier.keys.find((k) => k.type === 'Secp256k1' || k.type === 'Ed25519') if (!key) throw Error('No signing key for ' + identifier.did) //FIXME: Throw an `unsupported_format` error if the `args.proofFormat` is not `jwt` - const signer = (data: string | Uint8Array) => context.agent.keyManagerSign({ kid: key.kid, data }) - debug('Signing VC with', identifier.did) let alg = 'ES256K' if (key.type === 'Ed25519') { alg = 'EdDSA' } + const signer = wrapSigner(context, key, alg) const jwt = await createVerifiableCredentialJwt( credential, { did: identifier.did, signer, alg }, @@ -296,3 +296,10 @@ export class CredentialIssuer implements IAgentPlugin { } } } + +function wrapSigner(context: IAgentContext>, key: IKey, algorithm?: string) { + return async (data: string | Uint8Array) => { + const result = await context.agent.keyManagerSign({ keyRef: key.kid, data: data, algorithm }) + return result + } +} diff --git a/packages/data-store/plugin.schema.json b/packages/data-store/plugin.schema.json index 565b56e2a..b53026084 100644 --- a/packages/data-store/plugin.schema.json +++ b/packages/data-store/plugin.schema.json @@ -493,7 +493,7 @@ "credentialSubject", "proof" ], - "description": "Verifiable Credential {@link https://github.com/decentralized-identifier/did-jwt-vc}" + "description": "Verifiable Credential {@link https://github.com/decentralized-identifier/did-jwt-vc }" }, "VerifiablePresentation": { "type": "object", @@ -551,7 +551,7 @@ "verifiableCredential", "proof" ], - "description": "Verifiable Presentation {@link https://github.com/decentralized-identifier/did-jwt-vc}" + "description": "Verifiable Presentation {@link https://github.com/decentralized-identifier/did-jwt-vc }" }, "FindCredentialsArgs": { "$ref": "#/components/schemas/FindArgs-TCredentialColumns" diff --git a/packages/did-comm/plugin.schema.json b/packages/did-comm/plugin.schema.json index bd1e28b28..1f86baa36 100644 --- a/packages/did-comm/plugin.schema.json +++ b/packages/did-comm/plugin.schema.json @@ -54,7 +54,7 @@ "required": [ "data" ], - "description": "Input arguments for {@link IDIDComm.sendMessageDIDCommAlpha1}" + "description": "Input arguments for {@link IDIDComm.sendMessageDIDCommAlpha1 }" }, "IMessage": { "type": "object", @@ -240,7 +240,7 @@ "credentialSubject", "proof" ], - "description": "Verifiable Credential {@link https://github.com/decentralized-identifier/did-jwt-vc}" + "description": "Verifiable Credential {@link https://github.com/decentralized-identifier/did-jwt-vc }" }, "VerifiablePresentation": { "type": "object", @@ -298,7 +298,7 @@ "verifiableCredential", "proof" ], - "description": "Verifiable Presentation {@link https://github.com/decentralized-identifier/did-jwt-vc}" + "description": "Verifiable Presentation {@link https://github.com/decentralized-identifier/did-jwt-vc }" } }, "methods": { diff --git a/packages/did-provider-ethr/src/kms-eth-signer.ts b/packages/did-provider-ethr/src/kms-eth-signer.ts index d975f1b1c..10d4c0e27 100644 --- a/packages/did-provider-ethr/src/kms-eth-signer.ts +++ b/packages/did-provider-ethr/src/kms-eth-signer.ts @@ -37,10 +37,10 @@ export class KmsEthereumSigner extends Signer { delete tx.from } const signature = await this.context.agent.keyManagerSign({ - kid: this.controllerKey.kid, + keyRef: this.controllerKey.kid, data: serialize(tx), - alg: 'eth_signTransaction', - enc: 'base16', + algorithm: 'eth_signTransaction', + encoding: 'base16', }) return signature } diff --git a/packages/key-manager/src/abstract-key-management-system.ts b/packages/key-manager/src/abstract-key-management-system.ts index ee38fd9b5..46022149f 100644 --- a/packages/key-manager/src/abstract-key-management-system.ts +++ b/packages/key-manager/src/abstract-key-management-system.ts @@ -13,8 +13,9 @@ export abstract class AbstractKeyManagementSystem { async signEthTX({ key, transaction }: { key: IKey; transaction: object }): Promise { const { v, r, s, type, ...tx } = transaction const data = arrayify(serialize(tx)) - const alg = 'eth_signTransaction' - return this.sign({ key, data, alg }) + const algorithm = 'eth_signTransaction' + const signedTxHexString = this.sign({ key, data, algorithm }) + return signedTxHexString } /**@deprecated please use `sign({key, data})` instead, with `Uint8Array` data */ @@ -32,5 +33,5 @@ export abstract class AbstractKeyManagementSystem { return this.sign({ key, data: dataBytes }) } - abstract sign(args: { key: IKey; alg?: string; data: Uint8Array; [x: string]: any }): Promise + abstract sign(args: { key: IKey; algorithm?: string; data: Uint8Array; [x: string]: any }): Promise } diff --git a/packages/key-manager/src/key-manager.ts b/packages/key-manager/src/key-manager.ts index 68b99d6c7..dc7c86f36 100644 --- a/packages/key-manager/src/key-manager.ts +++ b/packages/key-manager/src/key-manager.ts @@ -116,22 +116,21 @@ export class KeyManager implements IAgentPlugin { /** {@inheritDoc @veramo/core#IKeyManager.keyManagerSign} */ async keyManagerSign(args: IKeyManagerSignArgs): Promise { - const { kid, data, alg, enc, extras } = args - const key = await this.store.get({ kid }) + const { keyRef, data, algorithm, encoding, ...extras } = { encoding: 'utf-8', ...args } + const key = await this.store.get({ kid: keyRef }) let dataBytes - const encoding = enc || 'utf-8' if (typeof data === 'string') { if (encoding === 'base16' || encoding === 'hex') { const preData = data.startsWith('0x') ? data.substring(2) : data dataBytes = u8a.fromString(preData, 'base16') } else { - dataBytes = u8a.fromString(data, encoding) + dataBytes = u8a.fromString(data, <'utf-8'>encoding) } } else { dataBytes = data } const kms = this.getKms(key.kms) - return kms.sign({ key, alg, data: dataBytes, ...extras }) + return kms.sign({ key, algorithm, data: dataBytes, ...extras }) } /** {@inheritDoc @veramo/core#IKeyManager.keyManagerSignEthTX} */ diff --git a/packages/kms-local-react-native/src/key-management-system.ts b/packages/kms-local-react-native/src/key-management-system.ts index d28a47f36..acd235b89 100644 --- a/packages/kms-local-react-native/src/key-management-system.ts +++ b/packages/kms-local-react-native/src/key-management-system.ts @@ -45,7 +45,7 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { } break default: - throw Error('Key type not supported: ' + type) + throw Error('not_supported: Key type not supported: ' + type) } debug('Created key', type, key.publicKeyHex) @@ -78,69 +78,106 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { return unpackMessage.message } - async sign({ - key, - alg, - data, - extras, - }: { - key: IKey - alg?: string - data: Uint8Array - extras?: KMSSignerExtras - }): Promise { + async sign({ key, algorithm, data }: { key: IKey; algorithm?: string; data: Uint8Array }): Promise { //FIXME: KMS implementation should not rely on private keys being provided, but rather manage their own keys if (!key.privateKeyHex) throw Error('No private key for kid: ' + key.kid) - if (key.type === 'Ed25519' && (typeof alg === 'undefined' || ['Ed25519', 'EdDSA'].includes(alg))) { - const signer = EdDSASigner(key.privateKeyHex) - const signature = await signer(data) - //base64url encoded string - return signature as string + if (key.type === 'Ed25519' && (typeof algorithm === 'undefined' || ['Ed25519', 'EdDSA'].includes(algorithm))) { + return await this.signEdDSA(key.privateKeyHex, data) } else if (key.type === 'Secp256k1') { - if (typeof alg === 'undefined' || ['ES256K', 'ES256K-R'].includes(alg)) { - const signer = ES256KSigner(key.privateKeyHex, alg === 'ES256K-R') - const signature = await signer(data) - //base64url encoded string - return signature as string - } else if (['eth_signTransaction', 'signTransaction', 'signTx'].includes(alg)) { - const { v, r, s, type, ...tx } = parse(data) - const wallet = new Wallet(key.privateKeyHex) - const signedRawTransaction = await wallet.signTransaction(tx) - //HEX encoded string, 0x prefixed - return signedRawTransaction - } else if (alg === 'eth_signMessage') { - const wallet = new Wallet(key.privateKeyHex) - const signature = await wallet.signMessage(data) - //HEX encoded string, 0x prefixed - return signature - } else if (['eth_signTypedData', 'EthereumEip712Signature2021'].includes(alg)) { - let msg, msgDomain, msgTypes - const serializedData = toUtf8String(data) - let jsonData = JSON.parse(serializedData) - if (typeof jsonData.domain === 'object' && typeof jsonData.types === 'object') { - const { domain = undefined, types = undefined, message = undefined } = { ...extras, ...jsonData } - msg = message || jsonData - msgDomain = domain - msgTypes = types - } - if (typeof msgDomain !== 'object' || typeof msgTypes !== 'object') { - throw Error(`invalid_arguments: Cannot sign typed data. 'domain' and 'types' must be provided`) - } - const wallet = new Wallet(key.privateKeyHex) + if (typeof algorithm === 'undefined' || ['ES256K', 'ES256K-R'].includes(algorithm)) { + return await this.signES256K(key.privateKeyHex, algorithm, data) + } else if (['eth_signTransaction', 'signTransaction', 'signTx'].includes(algorithm)) { + return await this.eth_signTransaction(key.privateKeyHex, data) + } else if (algorithm === 'eth_signMessage') { + return await this.eth_signMessage(key.privateKeyHex, data) + } else if (['eth_signTypedData', 'EthereumEip712Signature2021'].includes(algorithm)) { + return await this.eth_signTypedData(key.privateKeyHex, data) + } + } + throw Error(`not_supported: Cannot sign ${algorithm} using key of type ${key.type}`) + } - const signature = await wallet._signTypedData(msgDomain, msgTypes, msg) - //HEX encoded string - return signature + /** + * @returns a `0x` prefixed hex string representing the signed EIP712 data + */ + private async eth_signTypedData(privateKeyHex: string, data: Uint8Array) { + let msg, msgDomain, msgTypes + const serializedData = toUtf8String(data) + try { + let jsonData = JSON.parse(serializedData) + if (typeof jsonData.domain === 'object' && typeof jsonData.types === 'object') { + const { domain, types, message } = jsonData + msg = message + msgDomain = domain + msgTypes = types + } else { + // next check will throw since the data couldn't be parsed } + } catch (e) { + // next check will throw since the data couldn't be parsed + } + if (typeof msgDomain !== 'object' || typeof msgTypes !== 'object' || typeof msg !== 'object') { + throw Error( + `invalid_arguments: Cannot sign typed data. 'domain', 'types', and 'message' must be provided`, + ) } - throw Error(`not_supported: Cannot sign ${alg} using key of type ${key.type}`) + const wallet = new Wallet(privateKeyHex) + + const signature = await wallet._signTypedData(msgDomain, msgTypes, msg) + //HEX encoded string + return signature + } + + /** + * @returns a `0x` prefixed hex string representing the signed message + */ + private async eth_signMessage(privateKeyHex: string, rawMessageBytes: Uint8Array) { + const wallet = new Wallet(privateKeyHex) + const signature = await wallet.signMessage(rawMessageBytes) + //HEX encoded string, 0x prefixed + return signature + } + + /** + * @returns a `0x` prefixed hex string representing the signed raw transaction + */ + private async eth_signTransaction(privateKeyHex: string, rlpTransaction: Uint8Array) { + const { v, r, s, type, ...tx } = parse(rlpTransaction) + const wallet = new Wallet(privateKeyHex) + const signedRawTransaction = await wallet.signTransaction(tx) + //HEX encoded string, 0x prefixed + return signedRawTransaction + } + + /** + * @returns a base64url encoded signature for the `EdDSA` alg + */ + private async signEdDSA(key: string, data: Uint8Array): Promise { + const signer = EdDSASigner(key) + const signature = await signer(data) + //base64url encoded string + return signature as string + } + + /** + * @returns a base64url encoded signature for the `ES256K` or `ES256K-R` alg + */ + private async signES256K( + privateKeyHex: string, + alg: string | undefined, + data: Uint8Array, + ): Promise { + const signer = ES256KSigner(privateKeyHex, alg === 'ES256K-R') + const signature = await signer(data) + //base64url encoded string + return signature as string } } -interface KMSSignerExtras { - domain?: TypedDataDomain - types?: Record - transaction?: TransactionRequest - [x: string]: any +type Eip712Payload = { + domain: TypedDataDomain + types: Record + primaryType: string + message: Record } diff --git a/packages/kms-local/src/key-management-system.ts b/packages/kms-local/src/key-management-system.ts index 38ec0da8c..9064fff77 100644 --- a/packages/kms-local/src/key-management-system.ts +++ b/packages/kms-local/src/key-management-system.ts @@ -45,7 +45,7 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { } break default: - throw Error('Key type not supported: ' + type) + throw Error('not_supported: Key type not supported: ' + type) } debug('Created key', type, key.publicKeyHex) @@ -78,69 +78,106 @@ export class KeyManagementSystem extends AbstractKeyManagementSystem { return unpackMessage.message } - async sign({ - key, - alg, - data, - extras, - }: { - key: IKey - alg?: string - data: Uint8Array - extras?: KMSSignerExtras - }): Promise { + async sign({ key, algorithm, data }: { key: IKey; algorithm?: string; data: Uint8Array }): Promise { //FIXME: KMS implementation should not rely on private keys being provided, but rather manage their own keys if (!key.privateKeyHex) throw Error('No private key for kid: ' + key.kid) - if (key.type === 'Ed25519' && (typeof alg === 'undefined' || ['Ed25519', 'EdDSA'].includes(alg))) { - const signer = EdDSASigner(key.privateKeyHex) - const signature = await signer(data) - //base64url encoded string - return signature as string + if (key.type === 'Ed25519' && (typeof algorithm === 'undefined' || ['Ed25519', 'EdDSA'].includes(algorithm))) { + return await this.signEdDSA(key.privateKeyHex, data) } else if (key.type === 'Secp256k1') { - if (typeof alg === 'undefined' || ['ES256K', 'ES256K-R'].includes(alg)) { - const signer = ES256KSigner(key.privateKeyHex, alg === 'ES256K-R') - const signature = await signer(data) - //base64url encoded string - return signature as string - } else if (['eth_signTransaction', 'signTransaction', 'signTx'].includes(alg)) { - const {v, r, s, type, ...tx} = parse(data) - const wallet = new Wallet(key.privateKeyHex) - const signedRawTransaction = await wallet.signTransaction(tx) - //HEX encoded string, 0x prefixed - return signedRawTransaction - } else if (alg === 'eth_signMessage') { - const wallet = new Wallet(key.privateKeyHex) - const signature = await wallet.signMessage(data) - //HEX encoded string, 0x prefixed - return signature - } else if (['eth_signTypedData', 'EthereumEip712Signature2021'].includes(alg)) { - let msg, msgDomain, msgTypes - const serializedData = toUtf8String(data) - let jsonData = JSON.parse(serializedData) - if (typeof jsonData.domain === 'object' && typeof jsonData.types === 'object') { - const { domain = undefined, types = undefined, message = undefined } = { ...extras, ...jsonData } - msg = message || jsonData - msgDomain = domain - msgTypes = types - } - if (typeof msgDomain !== 'object' || typeof msgTypes !== 'object') { - throw Error(`invalid_arguments: Cannot sign typed data. 'domain' and 'types' must be provided`) - } - const wallet = new Wallet(key.privateKeyHex) + if (typeof algorithm === 'undefined' || ['ES256K', 'ES256K-R'].includes(algorithm)) { + return await this.signES256K(key.privateKeyHex, algorithm, data) + } else if (['eth_signTransaction', 'signTransaction', 'signTx'].includes(algorithm)) { + return await this.eth_signTransaction(key.privateKeyHex, data) + } else if (algorithm === 'eth_signMessage') { + return await this.eth_signMessage(key.privateKeyHex, data) + } else if (['eth_signTypedData', 'EthereumEip712Signature2021'].includes(algorithm)) { + return await this.eth_signTypedData(key.privateKeyHex, data) + } + } + throw Error(`not_supported: Cannot sign ${algorithm} using key of type ${key.type}`) + } - const signature = await wallet._signTypedData(msgDomain, msgTypes, msg) - //HEX encoded string - return signature + /** + * @returns a `0x` prefixed hex string representing the signed EIP712 data + */ + private async eth_signTypedData(privateKeyHex: string, data: Uint8Array) { + let msg, msgDomain, msgTypes + const serializedData = toUtf8String(data) + try { + let jsonData = JSON.parse(serializedData) + if (typeof jsonData.domain === 'object' && typeof jsonData.types === 'object') { + const { domain, types, message } = jsonData + msg = message + msgDomain = domain + msgTypes = types + } else { + // next check will throw since the data couldn't be parsed } + } catch (e) { + // next check will throw since the data couldn't be parsed + } + if (typeof msgDomain !== 'object' || typeof msgTypes !== 'object' || typeof msg !== 'object') { + throw Error( + `invalid_arguments: Cannot sign typed data. 'domain', 'types', and 'message' must be provided`, + ) } - throw Error(`not_supported: Cannot sign ${alg} using key of type ${key.type}`) + const wallet = new Wallet(privateKeyHex) + + const signature = await wallet._signTypedData(msgDomain, msgTypes, msg) + //HEX encoded string + return signature + } + + /** + * @returns a `0x` prefixed hex string representing the signed message + */ + private async eth_signMessage(privateKeyHex: string, rawMessageBytes: Uint8Array) { + const wallet = new Wallet(privateKeyHex) + const signature = await wallet.signMessage(rawMessageBytes) + //HEX encoded string, 0x prefixed + return signature + } + + /** + * @returns a `0x` prefixed hex string representing the signed raw transaction + */ + private async eth_signTransaction(privateKeyHex: string, rlpTransaction: Uint8Array) { + const { v, r, s, type, ...tx } = parse(rlpTransaction) + const wallet = new Wallet(privateKeyHex) + const signedRawTransaction = await wallet.signTransaction(tx) + //HEX encoded string, 0x prefixed + return signedRawTransaction + } + + /** + * @returns a base64url encoded signature for the `EdDSA` alg + */ + private async signEdDSA(key: string, data: Uint8Array): Promise { + const signer = EdDSASigner(key) + const signature = await signer(data) + //base64url encoded string + return signature as string + } + + /** + * @returns a base64url encoded signature for the `ES256K` or `ES256K-R` alg + */ + private async signES256K( + privateKeyHex: string, + alg: string | undefined, + data: Uint8Array, + ): Promise { + const signer = ES256KSigner(privateKeyHex, alg === 'ES256K-R') + const signature = await signer(data) + //base64url encoded string + return signature as string } } -interface KMSSignerExtras { - domain?: TypedDataDomain - types?: Record - transaction?: TransactionRequest - [x: string]: any +type Eip712Payload = { + domain: TypedDataDomain + types: Record + primaryType: string + message: Record } diff --git a/packages/selective-disclosure/plugin.schema.json b/packages/selective-disclosure/plugin.schema.json index b5813db01..a435b1aa6 100644 --- a/packages/selective-disclosure/plugin.schema.json +++ b/packages/selective-disclosure/plugin.schema.json @@ -97,7 +97,7 @@ "verifiableCredential", "proof" ], - "description": "Verifiable Presentation {@link https://github.com/decentralized-identifier/did-jwt-vc}" + "description": "Verifiable Presentation {@link https://github.com/decentralized-identifier/did-jwt-vc }" }, "VerifiableCredential": { "type": "object", @@ -174,7 +174,7 @@ "credentialSubject", "proof" ], - "description": "Verifiable Credential {@link https://github.com/decentralized-identifier/did-jwt-vc}" + "description": "Verifiable Credential {@link https://github.com/decentralized-identifier/did-jwt-vc }" }, "ICreateSelectiveDisclosureRequestArgs": { "type": "object", @@ -240,11 +240,11 @@ }, "credentialType": { "type": "string", - "description": "The credential type. See {@link https://www.w3.org/TR/vc-data-model/#types | W3C Credential Types}" + "description": "The credential type. See {@link https://www.w3.org/TR/vc-data-model/#types | W3C Credential Types }" }, "credentialContext": { "type": "string", - "description": "The credential context. See {@link https://www.w3.org/TR/vc-data-model/#contexts | W3C Credential Context}" + "description": "The credential context. See {@link https://www.w3.org/TR/vc-data-model/#contexts | W3C Credential Context }" }, "claimType": { "type": "string", @@ -345,11 +345,11 @@ }, "credentialType": { "type": "string", - "description": "The credential type. See {@link https://www.w3.org/TR/vc-data-model/#types | W3C Credential Types}" + "description": "The credential type. See {@link https://www.w3.org/TR/vc-data-model/#types | W3C Credential Types }" }, "credentialContext": { "type": "string", - "description": "The credential context. See {@link https://www.w3.org/TR/vc-data-model/#contexts | W3C Credential Context}" + "description": "The credential context. See {@link https://www.w3.org/TR/vc-data-model/#contexts | W3C Credential Context }" }, "claimType": { "type": "string", diff --git a/packages/selective-disclosure/src/action-handler.ts b/packages/selective-disclosure/src/action-handler.ts index a3d66a0ec..1835ba4f8 100644 --- a/packages/selective-disclosure/src/action-handler.ts +++ b/packages/selective-disclosure/src/action-handler.ts @@ -68,15 +68,15 @@ export class SelectiveDisclosure implements IAgentPlugin { const key = identifier.keys.find((k) => k.type === 'Secp256k1') if (!key) throw Error('Signing key not found') const signer = (data: string | Uint8Array) => { - let dataString, enc: 'base16' | undefined + let dataString, encoding: 'base16' | undefined if (typeof(data) === 'string') { dataString = data - enc = undefined + encoding = undefined } else { dataString = Buffer.from(data).toString("hex"), - enc = 'base16' + encoding = 'base16' } - return context.agent.keyManagerSign({ kid: key.kid, data: dataString, enc }) + return context.agent.keyManagerSign({ keyRef: key.kid, data: dataString, encoding }) } const jwt = await createJWT( { diff --git a/yarn.lock b/yarn.lock index 7a64c8a01..12ec46739 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5024,6 +5024,11 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +electron-to-chromium@^1.3.723: + version "1.3.743" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.743.tgz#fcec24d6d647cb84fd796b42caa1b4039a180894" + integrity sha512-K2wXfo9iZQzNJNx67+Pld0DRF+9bYinj62gXCdgPhcu1vidwVuLPHQPPFnCdO55njWigXXpfBiT90jGUPbw8Zg== + elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"