diff --git a/packages/chain-helpers/src/blockchain/Blockchain.ts b/packages/chain-helpers/src/blockchain/Blockchain.ts index 37812a189..c22e90e56 100644 --- a/packages/chain-helpers/src/blockchain/Blockchain.ts +++ b/packages/chain-helpers/src/blockchain/Blockchain.ts @@ -175,6 +175,30 @@ export type TransactionSigner = SignerInterface< KiltAddress > +/** + * Signs a SubmittableExtrinsic. + * + * @param tx An unsigned SubmittableExtrinsic. + * @param signer The {@link KeyringPair} used to sign the tx. + * @param opts Additional options. + * @param opts.tip Optional amount of Femto-KILT to tip the validator. + * @returns A signed {@link SubmittableExtrinsic}. + */ +export async function signTx( + tx: SubmittableExtrinsic, + signer: KeyringPair | TransactionSigner, + { tip }: { tip?: AnyNumber } = {} +): Promise { + if ('address' in signer) { + return tx.signAsync(signer, { tip }) + } + + return tx.signAsync(signer.id, { + tip, + signer: Signers.getPolkadotSigner([signer]), + }) +} + /** * Signs and submits the SubmittableExtrinsic with optional resolution and rejection criteria. * diff --git a/packages/sdk-js/package.json b/packages/sdk-js/package.json index 233c761fb..eb46054f3 100644 --- a/packages/sdk-js/package.json +++ b/packages/sdk-js/package.json @@ -46,7 +46,9 @@ "@kiltprotocol/credentials": "workspace:*", "@kiltprotocol/did": "workspace:*", "@kiltprotocol/type-definitions": "^0.35.0", - "@kiltprotocol/utils": "workspace:*" + "@kiltprotocol/utils": "workspace:*", + "@polkadot/api": "^12.0.0", + "@polkadot/util": "^13.0.0" }, "peerDependencies": { "@kiltprotocol/augment-api": "^1.11210.0" diff --git a/packages/sdk-js/src/DidHelpers/common.ts b/packages/sdk-js/src/DidHelpers/common.ts index 2c3c39944..266cd628b 100644 --- a/packages/sdk-js/src/DidHelpers/common.ts +++ b/packages/sdk-js/src/DidHelpers/common.ts @@ -41,6 +41,15 @@ function mapError(err: SpRuntimeDispatchError, api: ApiPromise): Error { return new Error(`${err.type}: ${err.value.toHuman()}`) } +function assertStatus(expected: string, actual?: string): void { + if (actual !== expected) { + const getterName = `as${expected.slice(0, 1).toUpperCase()}${expected.slice( + 1 + )}` + throw new Error(`can't access '${getterName}' when status is '${actual}'`) + } +} + export async function checkResultImpl( result: { blockHash: HexString; txHash: HexString } | SubmittableResultValue, api: ApiPromise, @@ -152,50 +161,49 @@ export async function checkResultImpl( let signers: Awaited> if (didDocument) { signers = await signersForDid(didDocument, ...signersOrKeys) + } else if (status === 'confirmed') { + status = 'unknown' + error = new Error('failed to fetch DID document') } + return { status, get asFailed(): TransactionResult['asFailed'] { - if (status !== 'failed') { - throw new Error('') - } + assertStatus('failed', status) return { - error: error!, + error: error ?? new Error('unknown error'), txHash: u8aToHex(u8aToU8a(result.txHash)), signers, didDocument, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion block: { hash: blockHash!, number: blockNumber! }, events: txEvents.map(({ event }) => event), } }, get asUnknown(): TransactionResult['asUnknown'] { - if (status !== 'unknown') { - throw new Error('') - } + assertStatus('unknown', status) return { error: error as Error, txHash: u8aToHex(u8aToU8a(result.txHash)), } }, get asRejected(): TransactionResult['asRejected'] { - if (status !== 'rejected') { - throw new Error('') - } + assertStatus('rejected', status) return { - error: error as Error, + error: error ?? new Error('unknown error'), txHash: u8aToHex(u8aToU8a(result.txHash)), signers, didDocument, } }, get asConfirmed(): TransactionResult['asConfirmed'] { - if (status !== 'confirmed') { - throw new Error('') - } + assertStatus('confirmed', status) return { txHash: u8aToHex(u8aToU8a(result.txHash)), signers, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion didDocument: didDocument!, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion block: { hash: blockHash!, number: blockNumber! }, events: txEvents.map(({ event }) => event), } @@ -228,28 +236,24 @@ export async function submitImpl( export function convertPublicKey(pk: AcceptedPublicKeyEncodings): { publicKey: Uint8Array - keyType: string + type: string } { - let didPublicKey: { - publicKey: Uint8Array - keyType: string - } + let publicKey: Uint8Array + let type: string + if (typeof pk === 'string') { - didPublicKey = multibaseKeyToDidKey(pk) + ;({ publicKey, keyType: type } = multibaseKeyToDidKey(pk)) } else if ('publicKeyMultibase' in pk) { - didPublicKey = multibaseKeyToDidKey( + ;({ publicKey, keyType: type } = multibaseKeyToDidKey( (pk as { publicKeyMultibase: KeyMultibaseEncoded }).publicKeyMultibase - ) + )) } else if ( 'publicKey' in pk && pk.publicKey.constructor.name === 'Uint8Array' ) { - didPublicKey = { - publicKey: pk.publicKey, - keyType: pk.type, - } + ;({ publicKey, type } = pk) } else { throw new Error('invalid public key') } - return didPublicKey + return { publicKey, type } } diff --git a/packages/sdk-js/src/DidHelpers/createDid.ts b/packages/sdk-js/src/DidHelpers/createDid.ts index 5a7d6b826..702d5d2ac 100644 --- a/packages/sdk-js/src/DidHelpers/createDid.ts +++ b/packages/sdk-js/src/DidHelpers/createDid.ts @@ -5,7 +5,13 @@ * found in the LICENSE file in the root directory of this source tree. */ -import { getFullDid, getStoreTx, signingMethodTypes } from '@kiltprotocol/did' +import { Blockchain } from '@kiltprotocol/chain-helpers' +import { + getFullDid, + getStoreTx, + type NewDidVerificationKey, + signingMethodTypes, +} from '@kiltprotocol/did' import type { KiltAddress, SignerInterface } from '@kiltprotocol/types' import { Crypto, Signers } from '@kiltprotocol/utils' @@ -23,8 +29,9 @@ function implementsSignerInterface(input: any): input is SignerInterface { /** * Creates an on-chain DID based on an authentication key. * + * @param options Any {@link SharedArguments} (minus `didDocument`) and additional parameters. * @param options.fromPublicKey The public key that will feature as the DID's initial authentication method and will determine the DID identifier. - * @param options + * @returns A set of {@link TransactionHandlers}. */ export function createDid( options: Omit & { @@ -36,10 +43,10 @@ export function createDid( ) => { const { fromPublicKey, submitter, signers, api } = options const { signSubmittable = true } = submitOptions - const { publicKey, keyType } = convertPublicKey(fromPublicKey) + const { publicKey, type } = convertPublicKey(fromPublicKey) - if (!signingMethodTypes.includes(keyType)) { - throw new Error('invalid public key') + if (!signingMethodTypes.includes(type)) { + throw new Error(`unknown key type ${type}`) } const submitterAccount = ( 'address' in submitter ? submitter.address : submitter.id @@ -58,29 +65,21 @@ export function createDid( }) ) ).flat() - const didCreation = await getStoreTx( + + let didCreation = await getStoreTx( { - authentication: [{ publicKey, type: keyType as 'sr25519' }], + authentication: [{ publicKey, type } as NewDidVerificationKey], }, submitterAccount, accountSigners ) - let signedHex if (signSubmittable) { - const signed = - 'address' in submitter - ? await didCreation.signAsync(submitter) - : await didCreation.signAsync(submitterAccount, { - signer: Signers.getPolkadotSigner([submitter]), - }) - signedHex = signed.toHex() - } else { - signedHex = didCreation.toHex() + didCreation = await Blockchain.signTx(didCreation, submitter) } return { - txHex: signedHex, + txHex: didCreation.toHex(), checkResult: (input) => checkResultImpl( input, diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index 488ad0941..876806254 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -5,14 +5,9 @@ * found in the LICENSE file in the root directory of this source tree. */ -import { signersForDid } from '@kiltprotocol/did' -import type { DidUrl, SignerInterface } from '@kiltprotocol/types' -import { Signers } from '@kiltprotocol/utils' - -import type { SharedArguments } from './interfaces.js' - export { createDid } from './createDid.js' export { deactivateDid } from './deactivateDid.js' +export { selectSigner } from './selectSigner.js' export { addService, removeService } from './service.js' export { transact } from './transact.js' export { @@ -20,37 +15,3 @@ export { setVerificationMethod, } from './verificationMethod.js' export { claimWeb3Name, releaseWeb3Name } from './w3names.js' - -/** - * Selects and returns a DID signer for a given purpose and algorithm. - * - * @param options All options. - * @param options.signers Signers from which to choose from; can also be `KeyringPair` instances or other key pair representations. - * @param options.relationship Which verification relationship the key should have to the DID. - * Defaults to `authentication`. - * @param options.algorithm Optionally filter signers by algorithm(s). - * @param options.didDocument The DID's DID document. - */ -export async function selectSigner({ - didDocument, - signers, - relationship = 'authentication', - algorithm, -}: Pick & { - relationship?: string - algorithm?: string | string[] -}): Promise | undefined> { - const mappedSigners = await signersForDid(didDocument, ...signers) - const selectors = [ - Signers.select.byDid(didDocument, { - verificationRelationship: relationship, - }), - ] - if (typeof algorithm !== 'undefined') { - Signers.select.byAlgorithm( - Array.isArray(algorithm) ? algorithm : [algorithm] - ) - } - - return Signers.selectSigner(mappedSigners, ...selectors) -} diff --git a/packages/sdk-js/src/DidHelpers/selectSigner.ts b/packages/sdk-js/src/DidHelpers/selectSigner.ts new file mode 100644 index 000000000..7f83b1ff6 --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/selectSigner.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2018-2024, BOTLabs GmbH. + * + * This source code is licensed under the BSD 4-Clause "Original" license + * found in the LICENSE file in the root directory of this source tree. + */ + +import { signersForDid } from '@kiltprotocol/did' +import type { DidUrl, SignerInterface } from '@kiltprotocol/types' +import { Signers } from '@kiltprotocol/utils' + +import type { SharedArguments } from './interfaces.js' + +/** + * Selects and returns a DID signer for a given purpose and algorithm. + * + * @param options All options. + * @param options.signers Signers from which to choose from; can also be `KeyringPair` instances or other key pair representations. + * @param options.relationship Which verification relationship the key should have to the DID. + * Defaults to `authentication`. + * @param options.algorithm Optionally filter signers by algorithm(s). + * @param options.didDocument The DID's DID document. + */ +export async function selectSigner({ + didDocument, + signers, + relationship = 'authentication', + algorithm, +}: Pick & { + relationship?: string + algorithm?: string | string[] +}): Promise | undefined> { + const mappedSigners = await signersForDid(didDocument, ...signers) + const selectors = [ + Signers.select.byDid(didDocument, { + verificationRelationship: relationship, + }), + ] + if (typeof algorithm !== 'undefined') { + Signers.select.byAlgorithm( + Array.isArray(algorithm) ? algorithm : [algorithm] + ) + } + + return Signers.selectSigner(mappedSigners, ...selectors) +} diff --git a/packages/sdk-js/src/DidHelpers/transact.ts b/packages/sdk-js/src/DidHelpers/transact.ts index 055b5c346..40eb7b767 100644 --- a/packages/sdk-js/src/DidHelpers/transact.ts +++ b/packages/sdk-js/src/DidHelpers/transact.ts @@ -7,9 +7,9 @@ import type { Extrinsic } from '@polkadot/types/interfaces' +import { Blockchain } from '@kiltprotocol/chain-helpers' import { authorizeTx, signersForDid } from '@kiltprotocol/did' import type { KiltAddress, SubmittableExtrinsic } from '@kiltprotocol/types' -import { Signers } from '@kiltprotocol/utils' import { checkResultImpl, submitImpl } from './common.js' import type { SharedArguments, TransactionHandlers } from './interfaces.js' @@ -17,9 +17,9 @@ import type { SharedArguments, TransactionHandlers } from './interfaces.js' /** * Instructs a transaction (state transition) as this DID (with this DID as the origin). * - * @param options + * @param options Any {@link SharedArguments} and additional parameters. * @param options.call The transaction / call to execute. - * @returns + * @returns A set of {@link TransactionHandlers}. */ export function transact( options: SharedArguments & { @@ -44,7 +44,7 @@ export function transact( 'address' in submitter ? submitter.address : submitter.id ) as KiltAddress - const authorized: SubmittableExtrinsic = await authorizeTx( + let authorized: SubmittableExtrinsic = await authorizeTx( didDocument, call, didSigners, @@ -56,21 +56,12 @@ export function transact( : {} ) - let signedHex if (signSubmittable) { - const signed = - 'address' in submitter - ? await authorized.signAsync(submitter) - : await authorized.signAsync(submitterAccount, { - signer: Signers.getPolkadotSigner([submitter]), - }) - signedHex = signed.toHex() - } else { - signedHex = authorized.toHex() + authorized = await Blockchain.signTx(authorized, submitter) } return { - txHex: signedHex, + txHex: authorized.toHex(), checkResult: (input) => checkResultImpl(input, api, expectedEvents, didDocument.id, signers), } diff --git a/packages/sdk-js/src/DidHelpers/verificationMethod.ts b/packages/sdk-js/src/DidHelpers/verificationMethod.ts index 21ec5c3d0..e394ae15a 100644 --- a/packages/sdk-js/src/DidHelpers/verificationMethod.ts +++ b/packages/sdk-js/src/DidHelpers/verificationMethod.ts @@ -40,50 +40,44 @@ export function setVerificationMethod( relationship: VerificationRelationship } ): TransactionHandlers { - const pk = convertPublicKey(options.publicKey) - let didKeyUpdateTx + const { publicKey, relationship, didDocument, api } = options + const pk = convertPublicKey(publicKey) - if (options.relationship === 'keyAgreement') { - const didEncryptionKey: NewDidEncryptionKey = { - publicKey: pk.publicKey, - type: pk.keyType as any, + let didKeyUpdateTx + switch (relationship) { + case 'keyAgreement': { + const txs: SubmittableExtrinsic[] = [] + didDocument.keyAgreement?.forEach((id) => + txs.push(api.tx.did.removeKeyAgreementKey(urlFragmentToChain(id))) + ) + txs.push( + api.tx.did.addKeyAgreementKey( + publicKeyToChain(pk as NewDidEncryptionKey) + ) + ) + didKeyUpdateTx = api.tx.utility.batchAll(txs) + break } - const txs: SubmittableExtrinsic[] = [] - options.didDocument.keyAgreement?.forEach((id) => - txs.push(options.api.tx.did.removeKeyAgreementKey(urlFragmentToChain(id))) - ) - txs.push( - options.api.tx.did.addKeyAgreementKey(publicKeyToChain(didEncryptionKey)) - ) - didKeyUpdateTx = options.api.tx.utility.batchAll(txs) - } else { - const didVerificationKey: NewDidVerificationKey = { - publicKey: pk.publicKey, - type: pk.keyType as any, + case 'authentication': { + didKeyUpdateTx = api.tx.did.setAuthenticationKey( + publicKeyToChain(pk as NewDidVerificationKey) + ) + break } - - switch (options.relationship) { - case 'authentication': { - didKeyUpdateTx = options.api.tx.did.setAuthenticationKey( - publicKeyToChain(didVerificationKey) - ) - break - } - case 'capabilityDelegation': { - didKeyUpdateTx = options.api.tx.did.setDelegationKey( - publicKeyToChain(didVerificationKey) - ) - break - } - case 'assertionMethod': { - didKeyUpdateTx = options.api.tx.did.setAttestationKey( - publicKeyToChain(didVerificationKey) - ) - break - } - default: { - throw new Error('unsupported relationship') - } + case 'capabilityDelegation': { + didKeyUpdateTx = api.tx.did.setDelegationKey( + publicKeyToChain(pk as NewDidVerificationKey) + ) + break + } + case 'assertionMethod': { + didKeyUpdateTx = api.tx.did.setAttestationKey( + publicKeyToChain(pk as NewDidVerificationKey) + ) + break + } + default: { + throw new Error('unsupported relationship') } } @@ -111,18 +105,15 @@ export function removeVerificationMethod( relationship: Omit } ): TransactionHandlers { + const { relationship, didDocument, api, verificationMethodId } = options let didKeyUpdateTx - switch (options.relationship) { + switch (relationship) { case 'authentication': { throw new Error('authentication verification methods can not be removed') } case 'capabilityDelegation': { - if ( - options.didDocument.capabilityDelegation?.includes( - options.verificationMethodId - ) - ) { - didKeyUpdateTx = options.api.tx.did.removeDelegationKey() + if (didDocument.capabilityDelegation?.includes(verificationMethodId)) { + didKeyUpdateTx = api.tx.did.removeDelegationKey() } else { throw new Error( 'the specified capabilityDelegation method does not exist in the DID Document' @@ -131,11 +122,9 @@ export function removeVerificationMethod( break } case 'keyAgreement': { - if ( - options.didDocument.keyAgreement?.includes(options.verificationMethodId) - ) { - didKeyUpdateTx = options.api.tx.did.removeKeyAgreementKey( - urlFragmentToChain(options.verificationMethodId) + if (didDocument.keyAgreement?.includes(verificationMethodId)) { + didKeyUpdateTx = api.tx.did.removeKeyAgreementKey( + urlFragmentToChain(verificationMethodId) ) } else { throw new Error( @@ -145,12 +134,8 @@ export function removeVerificationMethod( break } case 'assertionMethod': { - if ( - options.didDocument.assertionMethod?.includes( - options.verificationMethodId - ) - ) { - didKeyUpdateTx = options.api.tx.did.removeAttestationKey() + if (didDocument.assertionMethod?.includes(verificationMethodId)) { + didKeyUpdateTx = api.tx.did.removeAttestationKey() } else { throw new Error( 'the specified assertionMethod does not exist in the DID Document' diff --git a/yarn.lock b/yarn.lock index 50b27b8c1..005cd363b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2205,7 +2205,9 @@ __metadata: "@kiltprotocol/did": "workspace:*" "@kiltprotocol/type-definitions": "npm:^0.35.0" "@kiltprotocol/utils": "workspace:*" + "@polkadot/api": "npm:^12.0.0" "@polkadot/typegen": "npm:^12.0.0" + "@polkadot/util": "npm:^13.0.0" rimraf: "npm:^3.0.2" terser-webpack-plugin: "npm:^5.1.1" typescript: "npm:^4.8.3"