Skip to content

refactor: various did helper refactors, code deduplication & lint fixes #882

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions packages/chain-helpers/src/blockchain/Blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<SubmittableExtrinsic> {
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.
*
Expand Down
4 changes: 3 additions & 1 deletion packages/sdk-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
58 changes: 31 additions & 27 deletions packages/sdk-js/src/DidHelpers/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -152,50 +161,49 @@ export async function checkResultImpl(
let signers: Awaited<ReturnType<typeof signersForDid>>
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),
}
Expand Down Expand Up @@ -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 }
}
35 changes: 17 additions & 18 deletions packages/sdk-js/src/DidHelpers/createDid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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<SharedArguments, 'didDocument'> & {
Expand All @@ -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
Expand All @@ -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,
Expand Down
41 changes: 1 addition & 40 deletions packages/sdk-js/src/DidHelpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,13 @@
* 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 {
removeVerificationMethod,
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<SharedArguments, 'didDocument' | 'signers'> & {
relationship?: string
algorithm?: string | string[]
}): Promise<SignerInterface<string, DidUrl> | 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)
}
46 changes: 46 additions & 0 deletions packages/sdk-js/src/DidHelpers/selectSigner.ts
Original file line number Diff line number Diff line change
@@ -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<SharedArguments, 'didDocument' | 'signers'> & {
relationship?: string
algorithm?: string | string[]
}): Promise<SignerInterface<string, DidUrl> | 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)
}
21 changes: 6 additions & 15 deletions packages/sdk-js/src/DidHelpers/transact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@

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'

/**
* 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 & {
Expand All @@ -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,
Expand All @@ -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),
}
Expand Down
Loading