From 22f79cc5782334efe6c719b3646ff189c3d4d704 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 2 Jul 2024 14:46:15 +0200 Subject: [PATCH 01/70] feat: create DID signers from signers or keypairs --- packages/did/src/Did.signature.ts | 84 ++++++++++++- packages/sdk-js/src/DidHelpers/index.ts | 45 +++++++ packages/sdk-js/src/DidHelpers/interfaces.ts | 117 +++++++++++++++++++ packages/utils/src/Signers.ts | 88 ++++++++------ 4 files changed, 295 insertions(+), 39 deletions(-) create mode 100644 packages/sdk-js/src/DidHelpers/index.ts create mode 100644 packages/sdk-js/src/DidHelpers/interfaces.ts diff --git a/packages/did/src/Did.signature.ts b/packages/did/src/Did.signature.ts index 97491e5c6..6dfb5a119 100644 --- a/packages/did/src/Did.signature.ts +++ b/packages/did/src/Did.signature.ts @@ -5,17 +5,22 @@ * found in the LICENSE file in the root directory of this source tree. */ -import { isHex } from '@polkadot/util' +import { isHex, u8aEq } from '@polkadot/util' +import type { Keypair } from '@polkadot/util-crypto/types' import type { - DidSignature, Did, + DidDocument, + DidSignature, DidUrl, + KeyringPair, SignatureVerificationRelationship, + SignerInterface, } from '@kiltprotocol/types' -import { Crypto, SDKErrors } from '@kiltprotocol/utils' +import { Crypto, SDKErrors, Signers } from '@kiltprotocol/utils' +import { decodeAddress, isAddress } from '@polkadot/util-crypto' import { multibaseKeyToDidKey, parse, validateDid } from './Did.utils.js' import { resolve } from './DidResolver/DidResolver.js' @@ -175,3 +180,76 @@ export function signatureFromJson(input: DidSignature | LegacyDidSignature): { const signature = Crypto.coToUInt8(input.signature) return { signature, signerUrl } } + +/** + * Creates signers for {@link Did} based on a {@link DidDocument} and one or more {@link Keypair} / {@link KeyringPair}. + * + * @param didDocument The Did's DidDocument. + * @param keypairs One or more signing key pairs. + * @returns An array of signers based on the key pair. + */ +export async function signersForDid( + didDocument: DidDocument, + ...keypairs: Array< + | SignerInterface + | KeyringPair + | Keypair + | { + secretKeyMultibase: `z${string}` + publicKeyMultibase: `z${string}` + } + > +): Promise>> { + const didKeys = didDocument.verificationMethod?.map( + ({ publicKeyMultibase, id }) => ({ + ...multibaseKeyToDidKey(publicKeyMultibase), + id, + publicKeyMultibase, + }) + ) + if (didKeys && didKeys.length !== 0) { + return ( + await Promise.all( + keypairs.map((keypair) => { + let keyMatches: (key: any) => boolean + if ('publicKeyMultibase' in keypair) { + keyMatches = ({ publicKeyMultibase }) => + publicKeyMultibase === keypair.publicKeyMultibase + } else if ('type' in keypair) { + keyMatches = ({ type, publicKey }) => + type === keypair.type && u8aEq(publicKey, keypair.publicKey) + } else if ('publicKey' in keypair) { + keyMatches = ({ publicKey }) => u8aEq(publicKey, keypair.publicKey) + } else if (keypair.id.startsWith('z')) { + keyMatches = ({ publicKeyMultibase }) => + publicKeyMultibase === keypair.id + } else if (isAddress(keypair.id)) { + const thisPubKey = decodeAddress(keypair.id) + keyMatches = ({ publicKey }) => u8aEq(publicKey, thisPubKey) + } else { + return [] + } + const matchingKey = didKeys.find(keyMatches) + if (matchingKey) { + const id: DidUrl = matchingKey.id.startsWith('#') + ? `${didDocument.id}${matchingKey.id}` + : (matchingKey.id as DidUrl) + if ('algorithm' in keypair) { + return { + ...keypair, + id, + } + } + return Signers.getSignersForKeypair({ + keypair, + id, + type: matchingKey.keyType, + }) + } + return [] + }) + ) + ).flat() + } + return [] +} diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts new file mode 100644 index 000000000..5e2b38147 --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -0,0 +1,45 @@ +/** + * 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 type { DidUrl, SignerInterface } from '@kiltprotocol/types' +import { signersForDid } from '@kiltprotocol/did' +import { Signers } from '@kiltprotocol/utils' +import type { SharedArguments } from './interfaces' + +/** + * 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/interfaces.ts b/packages/sdk-js/src/DidHelpers/interfaces.ts new file mode 100644 index 000000000..5a7f38ecc --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/interfaces.ts @@ -0,0 +1,117 @@ +/** + * 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 type { + DidDocument, + HexString, + KeyringPair, + KiltAddress, + SignerInterface, +} from '@kiltprotocol/types' +import type { ApiPromise } from '@polkadot/api' +import type { SubmittableResultValue } from '@polkadot/api/types' +import type { GenericEvent } from '@polkadot/types' + +export interface TransactionResult { + status: 'confirmed' | 'failed' | 'rejected' | 'unknown' + // these are getters that would throw if status is not as expected + asConfirmed: { + txHash: HexString + signers: SignerInterface[] + didDocument: DidDocument + block: { hash: HexString; number: BigInt } + events: GenericEvent[] + } + asFailed: { + error: Error + txHash: HexString + signers: SignerInterface[] + didDocument?: DidDocument + block: { hash: HexString; number: BigInt } + events: GenericEvent[] + } + asRejected: { + error: Error + txHash: HexString + signers: SignerInterface[] + didDocument?: DidDocument + } + asUnknown: { + error: Error + txHash: HexString + } + // we may or may not add these, given that you can also disambiguate based on the status + isConfirmed: boolean + isFailed: boolean + isRejected: boolean + isUnknown: boolean +} + +export interface TransactionHandlers { + /** + * Submits a transaction for inclusion in a block, resulting in its execution in the blockchain runtime. + * + * @param options Options map to allow for named arguments. + * @param options.awaitFinalized If set to true, this waits for finalization of the block of inclusion before returning. + * @param options.submitterAccount Sets or overrides the account which produces the outer signature on the transaction and thus covers submission fees. + * @returns A Promise resolving to the DID document and info on the success of the transaction. + */ + submit(options?: { + awaitFinalized?: boolean // default: false + timeout?: number // in seconds + }): Promise + /** + * Produces a transaction that can be submitted to a blockchain node for inclusion, or signed and submitted by an external service. + * + * @param options Options map to allow for named arguments. + * @param options.signSubmittable If set to true, this signs the transaction with the submitterAccount, which covers transaction fees. + * In this case, if no signer is available for this account, the function throws. + * @param options.submitterAccount Sets or overrides the account which produces the outer signature on the transaction and thus covers submission fees. + * @returns A Promise resolving to an Extrinsic object (encoded transaction). + */ + getSubmittable(options?: { + signSubmittable?: boolean // default: true + }): Promise<{ + txHex: HexString + /** + * Takes info on the submission/inclusion of the transaction and evaluates whether it executed successfully. + * + * @param result A combination of the final transaction hash (which changes upon signing!) and the hash of the block in which the transaction was included; + * or constructor parameters for a {@link SubmittableResult}. + * @returns An object informing on the status and success of the transaction. + */ + checkResult( + result: + | { blockHash: HexString; txHash: HexString } + | SubmittableResultValue + ): Promise + }> +} + +/** Base58 encoded bytes, using the bitcoin alphabet. */ +type Base58Btc = string +/** Multibase encoding of a public- or private key including multicodec variant flag. */ +type KeyMultibaseEncoded = `z${Base58Btc}` + +export type SharedArguments = { + didDocument: DidDocument + api: ApiPromise + signers: Array< + | SignerInterface + | KeyringPair + | { + secretKeyMultibase: KeyMultibaseEncoded + publicKeyMultibase: KeyMultibaseEncoded + } + > + submitterAccount: KiltAddress +} + +export type AcceptedPublicKeyEncodings = + | KeyMultibaseEncoded + | { publicKeyMultibase: KeyMultibaseEncoded } + | Pick // interface allows KeyringPair too diff --git a/packages/utils/src/Signers.ts b/packages/utils/src/Signers.ts index c09e95a30..74fa9fe1e 100644 --- a/packages/utils/src/Signers.ts +++ b/packages/utils/src/Signers.ts @@ -21,24 +21,26 @@ import { } from '@polkadot/util-crypto' import type { Keypair } from '@polkadot/util-crypto/types' -import { - createSigner as es256kSigner, - cryptosuite as es256kSuite, -} from '@kiltprotocol/es256k-jcs-2023' import { createSigner as ed25519Signer, cryptosuite as ed25519Suite, } from '@kiltprotocol/eddsa-jcs-2022' import { - cryptosuite as sr25519Suite, + createSigner as es256kSigner, + cryptosuite as es256kSuite, +} from '@kiltprotocol/es256k-jcs-2023' +import { decodeMultikeyVerificationMethod } from '@kiltprotocol/jcs-data-integrity-proofs-common' +import { createSigner as sr25519Signer, + cryptosuite as sr25519Suite, } from '@kiltprotocol/sr25519-jcs-2023' +import { multibaseKeyToDidKey } from '@kiltprotocol/did' import type { - SignerInterface, DidDocument, DidUrl, KeyringPair, + SignerInterface, UriFragment, } from '@kiltprotocol/types' @@ -126,10 +128,10 @@ export async function ethereumEcdsaSigner({ * @param pair The pair, where the private key is inaccessible. * @returns The private key as a byte sequence. */ -function extractPk(pair: KeyringPair): Uint8Array { +function extractPk(pair: Pick): Keypair { const encoded = pair.encodePkcs8() - const { secretKey } = decodePair(undefined, encoded, 'none') - return secretKey + const { secretKey, publicKey } = decodePair(undefined, encoded, 'none') + return { secretKey, publicKey } } const signerFactory = { @@ -170,30 +172,25 @@ export async function signerFromKeypair< throw new Error(`unknown algorithm ${algorithm}`) } - if (!('secretKey' in keypair) && 'encodePkcs8' in keypair) { - const signerId = id ?? (keypair.address as Id) - return { - id: signerId, - algorithm, - sign: async (signData) => { - // TODO: can probably be optimized; but care must be taken to respect keyring locking - const secretKey = extractPk(keypair) - const { sign } = await makeSigner({ - secretKey, - publicKey: keypair.publicKey, - id: signerId, - }) - return sign(signData) - }, - } + if ('secretKey' in keypair && 'publicKey' in keypair) { + const { secretKey, publicKey } = keypair + return makeSigner({ + secretKey, + publicKey, + id: id ?? (encodeAddress(publicKey, 38) as Id), + }) + } + + if ('encodePkcs8' in keypair) { + const { secretKey, publicKey } = extractPk(keypair) + return makeSigner({ + secretKey, + publicKey, + id: id ?? (keypair.address as Id), + }) } - const { secretKey, publicKey } = keypair - return makeSigner({ - secretKey, - publicKey, - id: id ?? (encodeAddress(publicKey, 38) as Id), - }) + throw new Error('') } function algsForKeyType(keyType: string): KnownAlgorithms[] { @@ -227,19 +224,38 @@ function algsForKeyType(keyType: string): KnownAlgorithms[] { export async function getSignersForKeypair({ id, keypair, - type = (keypair as KeyringPair).type, + type, }: { id?: Id - keypair: Keypair | KeyringPair + keypair: + | Keypair + | KeyringPair + | { + secretKeyMultibase: `z${string}` + publicKeyMultibase: `z${string}` + } type?: string }): Promise>> { - if (!type) { + let pair: KeyringPair | (Keypair & { type: string }) + if ('publicKeyMultibase' in keypair) { + const { publicKey, keyType } = multibaseKeyToDidKey( + keypair.publicKeyMultibase + ) + const { publicKey: secretKey } = decodeMultikeyVerificationMethod({ + publicKeyMultibase: keypair.secretKeyMultibase, + }) + pair = { publicKey, secretKey, type: keyType } + } else if ('type' in keypair) { + pair = keypair + } else if (type) { + pair = { ...keypair, type } + } else { throw new Error('type is required if keypair.type is not given') } - const algorithms = algsForKeyType(type) + const algorithms = algsForKeyType(pair.type) return Promise.all( algorithms.map(async (algorithm) => { - return signerFromKeypair({ keypair, id, algorithm }) + return signerFromKeypair({ keypair: pair, id, algorithm }) }) ) } From d29002f9cebb33f60d5340ab84d0ae497f84ce22 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Mon, 8 Jul 2024 17:39:26 +0200 Subject: [PATCH 02/70] feat: transact first draft --- packages/sdk-js/src/DidHelpers/index.ts | 216 ++++++++++++++++++- packages/sdk-js/src/DidHelpers/interfaces.ts | 62 ++++-- 2 files changed, 257 insertions(+), 21 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index 5e2b38147..74369578f 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -5,10 +5,31 @@ * found in the LICENSE file in the root directory of this source tree. */ -import type { DidUrl, SignerInterface } from '@kiltprotocol/types' -import { signersForDid } from '@kiltprotocol/did' +import type { + Did, + DidUrl, + ISubmittableResult, + ResolutionResult, + SignerInterface, + SubmittableExtrinsic, +} from '@kiltprotocol/types' +import { authorizeTx, signersForDid } from '@kiltprotocol/did' import { Signers } from '@kiltprotocol/utils' -import type { SharedArguments } from './interfaces' + +import { + IS_FINALIZED, + IS_IN_BLOCK, + signAndSubmitTx, +} from 'chain-helpers/src/blockchain/Blockchain' +import type { Call, Extrinsic, Event } from '@polkadot/types/interfaces' +import { getSignersForKeypair } from 'utils/src/Signers' +import { u8aToHex } from 'utils/src/Crypto' +import type { + SharedArguments, + TransactionHandlers, + TransactionResult, +} from './interfaces' +import { DidResolver } from '..' /** * Selects and returns a DID signer for a given purpose and algorithm. @@ -43,3 +64,192 @@ export async function selectSigner({ return Signers.selectSigner(mappedSigners, ...selectors) } + +/** + * Instructs a transaction (state transition) as this DID (with this DID as the origin). + * + * @param options + * @param options.call The transaction / call to execute. + * @returns + */ +export function transact( + options: SharedArguments & { + call: Extrinsic + methods: string[] + } +): TransactionHandlers { + const submit: TransactionHandlers['submit'] = async ({ + awaitFinalized = true, + timeout = 0, + } = {}) => { + const didSigners = await signersForDid( + options.didDocument, + ...options.signers + ) + const authorized: SubmittableExtrinsic = await authorizeTx( + options.didDocument.id, + options.call, + didSigners, + options.submitterAccount + ) + + let signedTx + try { + signedTx = await signAndSubmitTx( + authorized, + options.submitterAccountSigner, + { + resolveOn: awaitFinalized ? IS_FINALIZED : IS_IN_BLOCK, + } + ) + } catch (e) { + // ToDo return error result. + throw new Error('todo rejected or unknown') + } + + // signedTx.events[0].event.method + return signedTx + + // return signedTx + // .then((r): TransactionResult => { + // return { status: 'confirmed' } + // }) + } + + const getSubmittable: TransactionHandlers['getSubmittable'] = async ( + submitOptions: + | { + signSubmittable?: boolean // default: true + } + | undefined = {} + ) => { + const didSigners = await signersForDid( + options.didDocument, + ...options.signers + ) + const authorized: SubmittableExtrinsic = await authorizeTx( + options.didDocument.id, + options.call, + didSigners, + options.submitterAccount + ) + + let signedHex + if (submitOptions.signSubmittable) { + const signed = + 'address' in options.submitterAccountSigner + ? await authorized.signAsync(options.submitterAccountSigner) + : await authorized.signAsync(options.submitterAccountSigner.id, { + signer: Signers.getPolkadotSigner([ + options.submitterAccountSigner, + ]), + }) + signedHex = signed.toHex() + } else { + signedHex = authorized.toHex() + } + return { + txHex: signedHex, + } + // const hex = authorized.toHex() + } + + return { + submit, + getSubmittable, + } +} + +// either confirmed or failed because it's in block. +async function checkEvents( + did: Did, + didSigners: SignerInterface[], + result: ISubmittableResult, + expectedEvents: Array<{ section: string; method: string }> +): Promise { + const isSuccess = + result.dispatchError && + expectedEvents.every( + ({ section, method }) => + typeof result.findRecord(section, method) !== 'undefined' + ) + const {didDocument} = await DidResolver.resolve(did) + + const didResolutionSuccess = !!didDocument; + const status: TransactionResult['status'] = isSuccess && didResolutionSuccess ? 'confirmed' : 'failed' + + + return { + status, + get asFailed(): TransactionResult['asFailed'] { + if (status === 'failed') { + throw new Error('') + return { + error: new Error(""), + txHash: u8aToHex(result.txHash), + signers: didSigners, + didDocument?: didDocument, + block: { hash: HexString; number: BigInt }, + events: GenericEvent[] + } + } else { + throw new Error("can't be") + } + }, + get asUnknown(): TransactionResult['asUnknown'] { + throw new Error("can't be") + }, + get asRejected(): TransactionResult['asRejected'] { + throw new Error("can't be") + }, + get asConfirmed(): TransactionResult['asConfirmed'] { + throw new Error("can't be") + }, + } + + // if (result.dispatchError) { + // return { + // status: 'unknown', + // get asUnknown() { + // return { + // error: new Error(''), + // txHash: u8aToHex(result.txHash), + // } + // }, + // } + // } + // + // if (result.internalError) { + // return { + // status: 'failed', + // asFailed: { + // error: new Error(''), + // txHash: u8aToHex(result.txHash), + // }, + // } + // } + // + // // Check the expected number of events. + // if (result.events.length !== expectedMethods.length) { + // return { + // status: 'unknown', + // asUnknown: { + // error: new Error(''), + // txHash: u8aToHex(result.txHash), + // }, + // } + // } + // + // for (const event of result.events) { + // if (!expectedMethods.includes(event.event.method)) { + // return { + // status: 'unknown', + // asUnknown: { + // error: new Error(''), + // txHash: u8aToHex(result.txHash), + // }, + // } + // } + // } + return undefined +} diff --git a/packages/sdk-js/src/DidHelpers/interfaces.ts b/packages/sdk-js/src/DidHelpers/interfaces.ts index 5a7f38ecc..d990ebc51 100644 --- a/packages/sdk-js/src/DidHelpers/interfaces.ts +++ b/packages/sdk-js/src/DidHelpers/interfaces.ts @@ -8,6 +8,7 @@ import type { DidDocument, HexString, + ISubmittableResult, KeyringPair, KiltAddress, SignerInterface, @@ -15,8 +16,30 @@ import type { import type { ApiPromise } from '@polkadot/api' import type { SubmittableResultValue } from '@polkadot/api/types' import type { GenericEvent } from '@polkadot/types' +import { TransactionSigner } from 'chain-helpers/src/blockchain/Blockchain' +// export interface ISubmittableResult { +// readonly dispatchError?: DispatchError | undefined; +// readonly dispatchInfo?: DispatchInfo | undefined; +// readonly events: EventRecord[]; +// readonly internalError?: Error | undefined; +// readonly status: ExtrinsicStatus; +// readonly isCompleted: boolean; +// readonly isError: boolean; +// readonly isFinalized: boolean; +// readonly isInBlock: boolean; +// readonly isWarning: boolean; +// readonly txHash: Hash; +// readonly txIndex?: number | undefined; +// filterRecords(section: string, method: string): EventRecord[]; +// findRecord(section: string, method: string): EventRecord | undefined; +// toHuman(isExtended?: boolean): AnyJson; +// } export interface TransactionResult { + // confirmed: Everything worked as expected. Finalized is not guaranteed. + // failed: error thrown by runtime logic (in SubmittableResult's events) don't try again. + // rejected: transaction was not processed by the runtime, (invalid transaction by the node and wasn't packed in a block) you might try to submit transaction again. + // unknown: IO error, unexpected internal error ..etc. status: 'confirmed' | 'failed' | 'rejected' | 'unknown' // these are getters that would throw if status is not as expected asConfirmed: { @@ -45,10 +68,10 @@ export interface TransactionResult { txHash: HexString } // we may or may not add these, given that you can also disambiguate based on the status - isConfirmed: boolean - isFailed: boolean - isRejected: boolean - isUnknown: boolean + // isConfirmed: boolean + // isFailed: boolean + // isRejected: boolean + // isUnknown: boolean } export interface TransactionHandlers { @@ -63,7 +86,7 @@ export interface TransactionHandlers { submit(options?: { awaitFinalized?: boolean // default: false timeout?: number // in seconds - }): Promise + }): Promise /** * Produces a transaction that can be submitted to a blockchain node for inclusion, or signed and submitted by an external service. * @@ -84,11 +107,11 @@ export interface TransactionHandlers { * or constructor parameters for a {@link SubmittableResult}. * @returns An object informing on the status and success of the transaction. */ - checkResult( - result: - | { blockHash: HexString; txHash: HexString } - | SubmittableResultValue - ): Promise + // checkResult( + // result: + // | { blockHash: HexString; txHash: HexString } + // | SubmittableResultValue + // ): Promise }> } @@ -97,18 +120,21 @@ type Base58Btc = string /** Multibase encoding of a public- or private key including multicodec variant flag. */ type KeyMultibaseEncoded = `z${Base58Btc}` +export type AcceptedSigners = + | SignerInterface + | KeyringPair + | { + secretKeyMultibase: KeyMultibaseEncoded + publicKeyMultibase: KeyMultibaseEncoded + } + export type SharedArguments = { didDocument: DidDocument api: ApiPromise - signers: Array< - | SignerInterface - | KeyringPair - | { - secretKeyMultibase: KeyMultibaseEncoded - publicKeyMultibase: KeyMultibaseEncoded - } - > + signers: AcceptedSigners[] + // TODO: remove submitter account. submitterAccount: KiltAddress + submitterAccountSigner: KeyringPair | TransactionSigner } export type AcceptedPublicKeyEncodings = From 24eddbad8ae68d0dfbb6f8148bb118f91a201ed8 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 9 Jul 2024 14:28:06 +0200 Subject: [PATCH 03/70] feat: transact full-featured implementation --- packages/sdk-js/src/DidHelpers/index.ts | 366 ++++++++++++------- packages/sdk-js/src/DidHelpers/interfaces.ts | 48 +-- 2 files changed, 236 insertions(+), 178 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index 74369578f..fd72eb6af 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -5,31 +5,36 @@ * found in the LICENSE file in the root directory of this source tree. */ -import type { - Did, - DidUrl, - ISubmittableResult, - ResolutionResult, - SignerInterface, - SubmittableExtrinsic, +import type { ApiPromise } from '@polkadot/api' +import type { SubmittableResultValue } from '@polkadot/api/types' +import type { BlockNumber, Extrinsic } from '@polkadot/types/interfaces' +import { u8aToHex, u8aToU8a } from '@polkadot/util' + +import { + FrameSystemEventRecord as EventRecord, + SpRuntimeDispatchError, +} from '@kiltprotocol/augment-api' +import { + authorizeTx, + signersForDid, + resolver as DidResolver, +} from '@kiltprotocol/did' +import { + KiltAddress, + type Did, + type DidUrl, + type HexString, + type SignerInterface, + type SubmittableExtrinsic, } from '@kiltprotocol/types' -import { authorizeTx, signersForDid } from '@kiltprotocol/did' import { Signers } from '@kiltprotocol/utils' +import { Blockchain } from '@kiltprotocol/chain-helpers' -import { - IS_FINALIZED, - IS_IN_BLOCK, - signAndSubmitTx, -} from 'chain-helpers/src/blockchain/Blockchain' -import type { Call, Extrinsic, Event } from '@polkadot/types/interfaces' -import { getSignersForKeypair } from 'utils/src/Signers' -import { u8aToHex } from 'utils/src/Crypto' import type { SharedArguments, TransactionHandlers, TransactionResult, -} from './interfaces' -import { DidResolver } from '..' +} from './interfaces.js' /** * Selects and returns a DID signer for a given purpose and algorithm. @@ -65,6 +70,167 @@ export async function selectSigner({ return Signers.selectSigner(mappedSigners, ...selectors) } +function mapError(err: SpRuntimeDispatchError, api: ApiPromise): Error { + if (err.isModule) { + const { docs, method, section } = api.findError(err.asModule.index.toU8a()) + return new Error(`${section}.${method}: ${docs}`) + } + return new Error(`${err.type}: ${err.value.toHuman()}`) +} + +async function checkResult( + result: { blockHash: HexString; txHash: HexString } | SubmittableResultValue, + api: ApiPromise, + expectedEvents: Array<{ section: string; method: string }>, + did: Did, + signers: SignerInterface[] +): Promise { + let txEvents: EventRecord[] = [] + let status: TransactionResult['status'] + let error: Error | undefined + let blockHash: HexString + let blockNumber: BigInt + if ('status' in result) { + txEvents = result.events ?? [] + switch (result.status.type) { + case 'Finalized': + case 'InBlock': + // this is the block hash for both + blockHash = result.status.value.toHex() + if ('blockNumber' in result) { + blockNumber = (result.blockNumber as BlockNumber).toBigInt() + } + break + case 'Dropped': + case 'FinalityTimeout': + case 'Invalid': + case 'Usurped': + status = 'rejected' + error = new Error(result.status.type) + break + case 'Broadcast': + case 'Future': + case 'Ready': + case 'Retracted': + status = 'unknown' + error = new Error(result.status.type) + break + default: + status = 'unknown' + error = new Error(`unknown tx status variant ${result.status.type}`) + } + } else if ( + 'blockHash' in result && + 'txHash' in result && + typeof result.blockHash === 'string' && + typeof result.txHash === 'string' + ) { + const txHashHash = api.createType('Hash', result.blockHash) + const { + block: { block }, + events, + } = await api.derive.tx.events(txHashHash) + blockNumber = block.header.number.toBigInt() + blockHash = result.blockHash + const txIndex = block.extrinsics.findIndex((tx) => + tx.hash.eq(result.txHash) + ) + txEvents = events.filter( + ({ phase }) => + phase.isApplyExtrinsic && phase.asApplyExtrinsic.eqn(txIndex) + ) + } else { + status = 'unknown' + error = new Error('missing blockHash and/or txHash') + } + if (typeof status !== 'string') { + txEvents.forEach(({ event }) => { + if (api.events.system.ExtrinsicFailed.is(event)) { + error = mapError(event.data[0], api) + } else if (api.events.proxy.ProxyExecuted.is(event)) { + const res = event.data[0] + if (res.isErr) { + error = mapError(res.asErr, api) + } + } else if (api.events.did.DidCallDispatched.is(event)) { + const res = event.data[1] + if (res.isErr) { + error = mapError(res.asErr, api) + } + } else if (api.events.utility.ItemFailed.is(event)) { + error = mapError(event.data[0], api) + } else if (api.events.utility.BatchInterrupted.is(event)) { + error = mapError(event.data[1], api) + } + }) + const isSuccess = + !error && + expectedEvents.every( + ({ section, method }) => + typeof txEvents.some( + ({ event }) => event.section === section && event.method === method + ) + ) + status = isSuccess ? 'confirmed' : 'failed' + } + + const { didDocument } = await DidResolver.resolve(did) + if (blockHash && !blockNumber) { + blockNumber = ( + await (await api.at(blockHash)).query.system.number() + ).toBigInt() + } + + return { + status, + get asFailed(): TransactionResult['asFailed'] { + if (status !== 'failed') { + throw new Error('') + } + return { + error: error!, + txHash: u8aToHex(u8aToU8a(result.txHash)), + signers, + didDocument, + block: { hash: blockHash, number: blockNumber }, + events: txEvents.map(({ event }) => event), + } + }, + get asUnknown(): TransactionResult['asUnknown'] { + if (status !== 'unknown') { + throw new Error('') + } + return { + error: error as Error, + txHash: u8aToHex(u8aToU8a(result.txHash)), + } + }, + get asRejected(): TransactionResult['asRejected'] { + if (status !== 'rejected') { + throw new Error('') + } + return { + error: error as Error, + txHash: u8aToHex(u8aToU8a(result.txHash)), + signers, + didDocument, + } + }, + get asConfirmed(): TransactionResult['asConfirmed'] { + if (status !== 'confirmed') { + throw new Error('') + } + return { + txHash: u8aToHex(u8aToU8a(result.txHash)), + signers, + didDocument: didDocument!, + block: { hash: blockHash, number: blockNumber }, + events: txEvents.map(({ event }) => event), + } + }, + } +} + /** * Instructs a transaction (state transition) as this DID (with this DID as the origin). * @@ -75,9 +241,15 @@ export async function selectSigner({ export function transact( options: SharedArguments & { call: Extrinsic - methods: string[] + expectedEvents: Array<{ section: string; method: string }> } ): TransactionHandlers { + const submitterAccount = ( + 'address' in options.submitter + ? options.submitter.address + : options.submitter.id + ) as KiltAddress + const submit: TransactionHandlers['submit'] = async ({ awaitFinalized = true, timeout = 0, @@ -90,30 +262,28 @@ export function transact( options.didDocument.id, options.call, didSigners, - options.submitterAccount + submitterAccount ) - let signedTx - try { - signedTx = await signAndSubmitTx( - authorized, - options.submitterAccountSigner, - { - resolveOn: awaitFinalized ? IS_FINALIZED : IS_IN_BLOCK, - } - ) - } catch (e) { - // ToDo return error result. - throw new Error('todo rejected or unknown') - } - - // signedTx.events[0].event.method - return signedTx + const result = await Blockchain.signAndSubmitTx( + authorized, + options.submitter, + { + resolveOn: awaitFinalized + ? (res) => res.isFinalized || res.isError + : (res) => res.isInBlock || res.isError, + rejectOn: () => false, + timeout, + } + ) - // return signedTx - // .then((r): TransactionResult => { - // return { status: 'confirmed' } - // }) + return checkResult( + result, + options.api, + options.expectedEvents, + options.didDocument.id, + didSigners + ) } const getSubmittable: TransactionHandlers['getSubmittable'] = async ( @@ -131,27 +301,33 @@ export function transact( options.didDocument.id, options.call, didSigners, - options.submitterAccount + submitterAccount ) let signedHex if (submitOptions.signSubmittable) { const signed = - 'address' in options.submitterAccountSigner - ? await authorized.signAsync(options.submitterAccountSigner) - : await authorized.signAsync(options.submitterAccountSigner.id, { - signer: Signers.getPolkadotSigner([ - options.submitterAccountSigner, - ]), + 'address' in options.submitter + ? await authorized.signAsync(options.submitter) + : await authorized.signAsync(submitterAccount, { + signer: Signers.getPolkadotSigner([options.submitter]), }) signedHex = signed.toHex() } else { signedHex = authorized.toHex() } + return { txHex: signedHex, + checkResult: (input) => + checkResult( + input, + options.api, + options.expectedEvents, + options.didDocument.id, + didSigners + ), } - // const hex = authorized.toHex() } return { @@ -159,97 +335,3 @@ export function transact( getSubmittable, } } - -// either confirmed or failed because it's in block. -async function checkEvents( - did: Did, - didSigners: SignerInterface[], - result: ISubmittableResult, - expectedEvents: Array<{ section: string; method: string }> -): Promise { - const isSuccess = - result.dispatchError && - expectedEvents.every( - ({ section, method }) => - typeof result.findRecord(section, method) !== 'undefined' - ) - const {didDocument} = await DidResolver.resolve(did) - - const didResolutionSuccess = !!didDocument; - const status: TransactionResult['status'] = isSuccess && didResolutionSuccess ? 'confirmed' : 'failed' - - - return { - status, - get asFailed(): TransactionResult['asFailed'] { - if (status === 'failed') { - throw new Error('') - return { - error: new Error(""), - txHash: u8aToHex(result.txHash), - signers: didSigners, - didDocument?: didDocument, - block: { hash: HexString; number: BigInt }, - events: GenericEvent[] - } - } else { - throw new Error("can't be") - } - }, - get asUnknown(): TransactionResult['asUnknown'] { - throw new Error("can't be") - }, - get asRejected(): TransactionResult['asRejected'] { - throw new Error("can't be") - }, - get asConfirmed(): TransactionResult['asConfirmed'] { - throw new Error("can't be") - }, - } - - // if (result.dispatchError) { - // return { - // status: 'unknown', - // get asUnknown() { - // return { - // error: new Error(''), - // txHash: u8aToHex(result.txHash), - // } - // }, - // } - // } - // - // if (result.internalError) { - // return { - // status: 'failed', - // asFailed: { - // error: new Error(''), - // txHash: u8aToHex(result.txHash), - // }, - // } - // } - // - // // Check the expected number of events. - // if (result.events.length !== expectedMethods.length) { - // return { - // status: 'unknown', - // asUnknown: { - // error: new Error(''), - // txHash: u8aToHex(result.txHash), - // }, - // } - // } - // - // for (const event of result.events) { - // if (!expectedMethods.includes(event.event.method)) { - // return { - // status: 'unknown', - // asUnknown: { - // error: new Error(''), - // txHash: u8aToHex(result.txHash), - // }, - // } - // } - // } - return undefined -} diff --git a/packages/sdk-js/src/DidHelpers/interfaces.ts b/packages/sdk-js/src/DidHelpers/interfaces.ts index d990ebc51..4d1fe380b 100644 --- a/packages/sdk-js/src/DidHelpers/interfaces.ts +++ b/packages/sdk-js/src/DidHelpers/interfaces.ts @@ -5,41 +5,19 @@ * found in the LICENSE file in the root directory of this source tree. */ +import type { ApiPromise } from '@polkadot/api' +import type { SubmittableResultValue } from '@polkadot/api/types' +import type { GenericEvent } from '@polkadot/types' + +import type { Blockchain } from '@kiltprotocol/chain-helpers' import type { DidDocument, HexString, - ISubmittableResult, KeyringPair, - KiltAddress, SignerInterface, } from '@kiltprotocol/types' -import type { ApiPromise } from '@polkadot/api' -import type { SubmittableResultValue } from '@polkadot/api/types' -import type { GenericEvent } from '@polkadot/types' -import { TransactionSigner } from 'chain-helpers/src/blockchain/Blockchain' -// export interface ISubmittableResult { -// readonly dispatchError?: DispatchError | undefined; -// readonly dispatchInfo?: DispatchInfo | undefined; -// readonly events: EventRecord[]; -// readonly internalError?: Error | undefined; -// readonly status: ExtrinsicStatus; -// readonly isCompleted: boolean; -// readonly isError: boolean; -// readonly isFinalized: boolean; -// readonly isInBlock: boolean; -// readonly isWarning: boolean; -// readonly txHash: Hash; -// readonly txIndex?: number | undefined; -// filterRecords(section: string, method: string): EventRecord[]; -// findRecord(section: string, method: string): EventRecord | undefined; -// toHuman(isExtended?: boolean): AnyJson; -// } export interface TransactionResult { - // confirmed: Everything worked as expected. Finalized is not guaranteed. - // failed: error thrown by runtime logic (in SubmittableResult's events) don't try again. - // rejected: transaction was not processed by the runtime, (invalid transaction by the node and wasn't packed in a block) you might try to submit transaction again. - // unknown: IO error, unexpected internal error ..etc. status: 'confirmed' | 'failed' | 'rejected' | 'unknown' // these are getters that would throw if status is not as expected asConfirmed: { @@ -86,7 +64,7 @@ export interface TransactionHandlers { submit(options?: { awaitFinalized?: boolean // default: false timeout?: number // in seconds - }): Promise + }): Promise /** * Produces a transaction that can be submitted to a blockchain node for inclusion, or signed and submitted by an external service. * @@ -107,11 +85,11 @@ export interface TransactionHandlers { * or constructor parameters for a {@link SubmittableResult}. * @returns An object informing on the status and success of the transaction. */ - // checkResult( - // result: - // | { blockHash: HexString; txHash: HexString } - // | SubmittableResultValue - // ): Promise + checkResult( + result: + | { blockHash: HexString; txHash: HexString } + | SubmittableResultValue + ): Promise }> } @@ -132,9 +110,7 @@ export type SharedArguments = { didDocument: DidDocument api: ApiPromise signers: AcceptedSigners[] - // TODO: remove submitter account. - submitterAccount: KiltAddress - submitterAccountSigner: KeyringPair | TransactionSigner + submitter: KeyringPair | Blockchain.TransactionSigner } export type AcceptedPublicKeyEncodings = From 70fa6203e01763d2f8cbaf9b2d2b43fb7235420d Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 9 Jul 2024 16:42:50 +0200 Subject: [PATCH 04/70] test: first unit test stub --- packages/did/src/Did.signature.ts | 5 +- packages/sdk-js/src/DidHelpers/index.spec.ts | 75 ++++++++++++++++++++ packages/sdk-js/src/DidHelpers/index.ts | 14 ++-- tests/testUtils/TestUtils.ts | 2 +- 4 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 packages/sdk-js/src/DidHelpers/index.spec.ts diff --git a/packages/did/src/Did.signature.ts b/packages/did/src/Did.signature.ts index 6dfb5a119..ac57fa130 100644 --- a/packages/did/src/Did.signature.ts +++ b/packages/did/src/Did.signature.ts @@ -211,13 +211,14 @@ export async function signersForDid( return ( await Promise.all( keypairs.map((keypair) => { + // TODO: fix typing let keyMatches: (key: any) => boolean if ('publicKeyMultibase' in keypair) { keyMatches = ({ publicKeyMultibase }) => publicKeyMultibase === keypair.publicKeyMultibase } else if ('type' in keypair) { - keyMatches = ({ type, publicKey }) => - type === keypair.type && u8aEq(publicKey, keypair.publicKey) + keyMatches = ({ keyType, publicKey }) => + keyType === keypair.type && u8aEq(publicKey, keypair.publicKey) } else if ('publicKey' in keypair) { keyMatches = ({ publicKey }) => u8aEq(publicKey, keypair.publicKey) } else if (keypair.id.startsWith('z')) { diff --git a/packages/sdk-js/src/DidHelpers/index.spec.ts b/packages/sdk-js/src/DidHelpers/index.spec.ts new file mode 100644 index 000000000..da94e9687 --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/index.spec.ts @@ -0,0 +1,75 @@ +import { ConfigService } from '../index.js' + +import { + ApiMocks, + createLocalDemoFullDidFromKeypair, +} from '../../../../tests/testUtils/index.js' + +jest.mock('@kiltprotocol/did', () => { + return { + ...jest.requireActual('@kiltprotocol/did'), + resolver: { resolve: jest.fn() }, + } +}) +import { resolver } from '@kiltprotocol/did' +import { transact } from './index.js' +import { Crypto } from '@kiltprotocol/utils' +import { DidDocument, KiltKeyringPair } from '@kiltprotocol/types' +// import { SubmittableResult } from '@polkadot/api' + +const mockedApi = ApiMocks.createAugmentedApi() + +describe('transact', () => { + let didDocument: DidDocument + let keypair: KiltKeyringPair + beforeAll(async () => { + ConfigService.set({ api: mockedApi }) + // jest + // .mocked(mockedApi.derive.tx.events) + // .mockResolvedValue(mockedApi.createType('')) + + keypair = Crypto.makeKeypairFromUri('//Alice') + didDocument = await createLocalDemoFullDidFromKeypair(keypair) + jest.mocked(resolver).resolve.mockImplementation((did) => { + if (did === didDocument.id) { + return { didDocument } as any + } + throw new Error() + }) + }) + + it('creates a tx and checks status', async () => { + const { txHex } = await transact({ + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + call: mockedApi.tx.attestation.add( + new Uint8Array(32).fill(1), + new Uint8Array(32).fill(1), + null + ), + expectedEvents: [ + { section: 'attestation', method: 'AttestationCreated' }, + ], + }).getSubmittable() + + expect(txHex).toContain('0x') + const parsed = mockedApi.tx(txHex) + expect(parsed.method).toMatchObject({ + section: 'did', + method: 'submitDidCall', + }) + // expect( + // checkResult( + // new SubmittableResult({ + // blockNumber: mockedApi.createType('BlockNumber', 1000), + // status: mockedApi.createType('ExtrinsicStatus', { + // inBlock: new Uint8Array(32).fill(2), + // }), + // txHash: parsed.hash, + // }) + // ) + // ) + }) +}) diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index fd72eb6af..afe6f5f23 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -86,10 +86,10 @@ async function checkResult( signers: SignerInterface[] ): Promise { let txEvents: EventRecord[] = [] - let status: TransactionResult['status'] + let status: TransactionResult['status'] | undefined let error: Error | undefined - let blockHash: HexString - let blockNumber: BigInt + let blockHash: HexString | undefined + let blockNumber: BigInt | undefined if ('status' in result) { txEvents = result.events ?? [] switch (result.status.type) { @@ -192,7 +192,7 @@ async function checkResult( txHash: u8aToHex(u8aToU8a(result.txHash)), signers, didDocument, - block: { hash: blockHash, number: blockNumber }, + block: { hash: blockHash!, number: blockNumber! }, events: txEvents.map(({ event }) => event), } }, @@ -224,7 +224,7 @@ async function checkResult( txHash: u8aToHex(u8aToU8a(result.txHash)), signers, didDocument: didDocument!, - block: { hash: blockHash, number: blockNumber }, + block: { hash: blockHash!, number: blockNumber! }, events: txEvents.map(({ event }) => event), } }, @@ -259,7 +259,7 @@ export function transact( ...options.signers ) const authorized: SubmittableExtrinsic = await authorizeTx( - options.didDocument.id, + options.didDocument, options.call, didSigners, submitterAccount @@ -298,7 +298,7 @@ export function transact( ...options.signers ) const authorized: SubmittableExtrinsic = await authorizeTx( - options.didDocument.id, + options.didDocument, options.call, didSigners, submitterAccount diff --git a/tests/testUtils/TestUtils.ts b/tests/testUtils/TestUtils.ts index a56399cea..0e1609695 100644 --- a/tests/testUtils/TestUtils.ts +++ b/tests/testUtils/TestUtils.ts @@ -20,7 +20,7 @@ import type { VerificationMethod, VerificationRelationship, } from '@kiltprotocol/types' -import { ConfigService } from '@kiltprotocol/sdk-js' +import { ConfigService } from '@kiltprotocol/config' import { Blockchain } from '@kiltprotocol/chain-helpers' import { Crypto, Signers, SDKErrors } from '@kiltprotocol/utils' import { From 90b91194b9969d0ed185d153dfb7cea174891544 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 9 Jul 2024 16:43:54 +0200 Subject: [PATCH 05/70] test: commenting out require cycle that broke tests (REVERT ME) --- packages/utils/src/Signers.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/utils/src/Signers.ts b/packages/utils/src/Signers.ts index 74fa9fe1e..e62400562 100644 --- a/packages/utils/src/Signers.ts +++ b/packages/utils/src/Signers.ts @@ -29,13 +29,13 @@ import { createSigner as es256kSigner, cryptosuite as es256kSuite, } from '@kiltprotocol/es256k-jcs-2023' -import { decodeMultikeyVerificationMethod } from '@kiltprotocol/jcs-data-integrity-proofs-common' +// import { decodeMultikeyVerificationMethod } from '@kiltprotocol/jcs-data-integrity-proofs-common' import { createSigner as sr25519Signer, cryptosuite as sr25519Suite, } from '@kiltprotocol/sr25519-jcs-2023' -import { multibaseKeyToDidKey } from '@kiltprotocol/did' +// import { multibaseKeyToDidKey } from '@kiltprotocol/did' import type { DidDocument, DidUrl, @@ -238,13 +238,14 @@ export async function getSignersForKeypair({ }): Promise>> { let pair: KeyringPair | (Keypair & { type: string }) if ('publicKeyMultibase' in keypair) { - const { publicKey, keyType } = multibaseKeyToDidKey( - keypair.publicKeyMultibase - ) - const { publicKey: secretKey } = decodeMultikeyVerificationMethod({ - publicKeyMultibase: keypair.secretKeyMultibase, - }) - pair = { publicKey, secretKey, type: keyType } + throw new Error('not implemented') + // const { publicKey, keyType } = multibaseKeyToDidKey( + // keypair.publicKeyMultibase + // ) + // const { publicKey: secretKey } = decodeMultikeyVerificationMethod({ + // publicKeyMultibase: keypair.secretKeyMultibase, + // }) + // pair = { publicKey, secretKey, type: keyType } } else if ('type' in keypair) { pair = keypair } else if (type) { From e90d3027752ebba86a1292a7f283a6341986836e Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 10 Jul 2024 10:09:54 +0200 Subject: [PATCH 06/70] test: some fixes to did helper tests --- packages/sdk-js/src/DidHelpers/index.spec.ts | 94 +++++++++++++++----- packages/sdk-js/src/DidHelpers/index.ts | 12 +-- packages/sdk-js/tsconfig.build.json | 3 + 3 files changed, 83 insertions(+), 26 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/index.spec.ts b/packages/sdk-js/src/DidHelpers/index.spec.ts index da94e9687..7fa6af607 100644 --- a/packages/sdk-js/src/DidHelpers/index.spec.ts +++ b/packages/sdk-js/src/DidHelpers/index.spec.ts @@ -1,21 +1,31 @@ -import { ConfigService } from '../index.js' +/** + * 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 { SubmittableResult } from '@polkadot/api' + +import { authorizeTx, resolver } from '@kiltprotocol/did' +import type { DidDocument, KiltKeyringPair } from '@kiltprotocol/types' +import { Crypto } from '@kiltprotocol/utils' +import { ConfigService } from '../index.js' import { ApiMocks, createLocalDemoFullDidFromKeypair, } from '../../../../tests/testUtils/index.js' +import { transact } from './index.js' +import { TransactionResult } from './interfaces.js' jest.mock('@kiltprotocol/did', () => { return { ...jest.requireActual('@kiltprotocol/did'), resolver: { resolve: jest.fn() }, + authorizeTx: jest.fn(), } }) -import { resolver } from '@kiltprotocol/did' -import { transact } from './index.js' -import { Crypto } from '@kiltprotocol/utils' -import { DidDocument, KiltKeyringPair } from '@kiltprotocol/types' -// import { SubmittableResult } from '@polkadot/api' const mockedApi = ApiMocks.createAugmentedApi() @@ -29,7 +39,33 @@ describe('transact', () => { // .mockResolvedValue(mockedApi.createType('')) keypair = Crypto.makeKeypairFromUri('//Alice') - didDocument = await createLocalDemoFullDidFromKeypair(keypair) + const { id, verificationMethod, authentication } = + await createLocalDemoFullDidFromKeypair(keypair, { + verificationRelationships: new Set(['assertionMethod']), + }) + didDocument = { + id, + authentication, + assertionMethod: authentication, + verificationMethod: verificationMethod?.filter( + (vm) => vm.id === authentication![0] + ), + } + jest + .mocked(authorizeTx) + .mockImplementation(async (did, ex, sig, sub, { txCounter } = {}) => { + // if(!sig.find(i => i.id)) {} + return mockedApi.tx.did.submitDidCall( + { + did: keypair.address, + txCounter: txCounter ?? 0, + call: ex, + blockNumber: 0, + submitter: sub, + }, + { ed25519: new Uint8Array(64) } + ) + }) jest.mocked(resolver).resolve.mockImplementation((did) => { if (did === didDocument.id) { return { didDocument } as any @@ -39,7 +75,7 @@ describe('transact', () => { }) it('creates a tx and checks status', async () => { - const { txHex } = await transact({ + const { txHex, checkResult } = await transact({ didDocument, api: mockedApi, submitter: keypair, @@ -52,7 +88,7 @@ describe('transact', () => { expectedEvents: [ { section: 'attestation', method: 'AttestationCreated' }, ], - }).getSubmittable() + }).getSubmittable({ signSubmittable: false }) expect(txHex).toContain('0x') const parsed = mockedApi.tx(txHex) @@ -60,16 +96,34 @@ describe('transact', () => { section: 'did', method: 'submitDidCall', }) - // expect( - // checkResult( - // new SubmittableResult({ - // blockNumber: mockedApi.createType('BlockNumber', 1000), - // status: mockedApi.createType('ExtrinsicStatus', { - // inBlock: new Uint8Array(32).fill(2), - // }), - // txHash: parsed.hash, - // }) - // ) - // ) + + await expect( + checkResult( + new SubmittableResult({ + blockNumber: mockedApi.createType('BlockNumber', 1000), + status: mockedApi.createType('ExtrinsicStatus', { + inBlock: new Uint8Array(32).fill(2), + }), + txHash: parsed.hash, + events: [ + mockedApi.createType('EventRecord', { + phase: { ApplyExtrinsic: 0 }, + event: { section: 'attestation', method: 'AttestationCreated' }, + }), + ], + }) + ) + ).resolves.toMatchObject>({ + status: 'confirmed', + asConfirmed: expect.objectContaining({ + txHash: parsed.hash.toHex(), + block: { + hash: mockedApi + .createType('Hash', new Uint8Array(32).fill(2)) + .toHex(), + number: 1000n, + }, + }), + }) }) }) diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index afe6f5f23..a312908ca 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -165,11 +165,10 @@ async function checkResult( }) const isSuccess = !error && - expectedEvents.every( - ({ section, method }) => - typeof txEvents.some( - ({ event }) => event.section === section && event.method === method - ) + expectedEvents.every(({ section, method }) => + txEvents.some( + ({ event }) => event.section === section && event.method === method + ) ) status = isSuccess ? 'confirmed' : 'failed' } @@ -305,7 +304,8 @@ export function transact( ) let signedHex - if (submitOptions.signSubmittable) { + const { signSubmittable = true } = submitOptions + if (signSubmittable) { const signed = 'address' in options.submitter ? await authorized.signAsync(options.submitter) diff --git a/packages/sdk-js/tsconfig.build.json b/packages/sdk-js/tsconfig.build.json index c7b062afb..e54a0b1c2 100644 --- a/packages/sdk-js/tsconfig.build.json +++ b/packages/sdk-js/tsconfig.build.json @@ -8,5 +8,8 @@ "include": [ "src/**/*.ts", "src/**/*.js" + ], + "exclude": [ + "**/*.spec.ts", ] } From 90d5ebe47c8d358c47854aeaafe562ca65f20a19 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Thu, 11 Jul 2024 09:18:04 +0200 Subject: [PATCH 07/70] fix: checkresult --- packages/sdk-js/src/DidHelpers/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index a312908ca..4a3af9ebd 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -95,6 +95,7 @@ async function checkResult( switch (result.status.type) { case 'Finalized': case 'InBlock': + status = "confirmed" // this is the block hash for both blockHash = result.status.value.toHex() if ('blockNumber' in result) { From 8479b2cb1573533a5d9791b564576fb598c083a6 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Mon, 15 Jul 2024 17:20:30 +0200 Subject: [PATCH 08/70] feat: createDid draft --- packages/did/src/DidDetails/index.ts | 1 + packages/sdk-js/src/DidHelpers/createDid.ts | 148 +++++++++++++++++++ packages/sdk-js/src/DidHelpers/index.spec.ts | 65 ++++++-- packages/sdk-js/src/DidHelpers/index.ts | 62 +++++--- packages/sdk-js/src/DidHelpers/interfaces.ts | 2 +- packages/sdk-js/src/index.ts | 2 + tests/integration/didHelpers.spec.ts | 62 ++++++++ 7 files changed, 307 insertions(+), 35 deletions(-) create mode 100644 packages/sdk-js/src/DidHelpers/createDid.ts create mode 100644 tests/integration/didHelpers.spec.ts diff --git a/packages/did/src/DidDetails/index.ts b/packages/did/src/DidDetails/index.ts index a80411c9d..ccdd0b106 100644 --- a/packages/did/src/DidDetails/index.ts +++ b/packages/did/src/DidDetails/index.ts @@ -19,6 +19,7 @@ export type { export { isValidDidVerificationType, isValidEncryptionMethodType, + signingMethodTypes } from './DidDetails.js' export * from './LightDidDetails.js' export * from './FullDidDetails.js' diff --git a/packages/sdk-js/src/DidHelpers/createDid.ts b/packages/sdk-js/src/DidHelpers/createDid.ts new file mode 100644 index 000000000..c1a5f6718 --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/createDid.ts @@ -0,0 +1,148 @@ +/** + * 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 { + DidVerificationMethodType, + getStoreTx, + keypairToMultibaseKey, + multibaseKeyToDidKey, + signingMethodTypes, +} from '@kiltprotocol/did' +import { KiltAddress, KeyringPair, SignerInterface } from '@kiltprotocol/types' +import * as Did from '@kiltprotocol/did' +import type { + DidDocument, + DidUrl, + VerificationMethod, +} from '@kiltprotocol/types' +import { Signers } from '@kiltprotocol/utils' +import type { + AcceptedPublicKeyEncodings, + KeyMultibaseEncoded, + SharedArguments, + TransactionHandlers, +} from './interfaces.js' +import { transact } from './index.js' + +function convertPublicKey(pk: AcceptedPublicKeyEncodings): { + publicKey: Uint8Array + keyType: string +} { + let didPublicKey: { + publicKey: Uint8Array + keyType: string + } + if (typeof pk === 'string') { + didPublicKey = multibaseKeyToDidKey(pk) + } else if ('publicKeyMultibase' in pk) { + didPublicKey = multibaseKeyToDidKey( + (pk as { publicKeyMultibase: KeyMultibaseEncoded }).publicKeyMultibase + ) + } else if ( + 'publicKey' in pk && + pk.publicKey.constructor.name === 'Uint8Array' + ) { + didPublicKey = { + publicKey: pk.publicKey, + keyType: pk.type, + } + } else { + throw new Error('invalid public key') + } + return didPublicKey +} + +/** + * Creates an on-chain DID based on an authentication key. + * + * @param options.fromPublicKey The public key that will feature as the DID's initial authentication method and will determine the DID identifier. + * @param options + */ +export async function createDid( + options: Omit & { + fromPublicKey: AcceptedPublicKeyEncodings + } +): Promise { + const didPublicKey = convertPublicKey(options.fromPublicKey) + + if (!signingMethodTypes.includes(didPublicKey.keyType)) { + throw new Error('invalid public key') + } + + const didPublicKeyMultibase = keypairToMultibaseKey({ + publicKey: didPublicKey.publicKey, + type: didPublicKey.keyType as DidVerificationMethodType, + }) + + const submitterAccount = ( + 'address' in options.submitter + ? options.submitter.address + : options.submitter.id + ) as KiltAddress + + const didAuthKey: Did.NewDidVerificationKey = { + publicKey: didPublicKey.publicKey, + type: didPublicKey.keyType as any, + } + + function implementsSignerInterface(input: any): boolean { + return 'algorithm' in input && 'id' in input && 'sign' in input + } + + const signers: SignerInterface[] = ( + await Promise.all( + options.signers + .map(async (signer) => { + if (implementsSignerInterface(signer)) { + return signer as SignerInterface + } + const res = (await Signers.getSignersForKeypair({ + keypair: signer as + | KeyringPair + | { + secretKeyMultibase: KeyMultibaseEncoded + publicKeyMultibase: KeyMultibaseEncoded + }, + })) as SignerInterface[] + return res + }) + .flat() + ) + ).flat() + + const fullDid = Did.getFullDidFromVerificationMethod({ + publicKeyMultibase: didPublicKeyMultibase, + }) + + const vm: VerificationMethod = { + id: `${fullDid}#auth` as DidUrl, + controller: fullDid, + type: 'Multikey', + publicKeyMultibase: didPublicKeyMultibase, + } + + const didDocument: DidDocument = { + id: fullDid, + verificationMethod: [vm], + authentication: [vm.id], + } + + const fullDidCreationTx = await getStoreTx( + { + authentication: [didAuthKey], + }, + submitterAccount, + signers + ) + + return transact({ + ...options, + didDocument, + call: fullDidCreationTx, + expectedEvents: [{ section: 'did', method: 'DidCreated' }], + }) +} diff --git a/packages/sdk-js/src/DidHelpers/index.spec.ts b/packages/sdk-js/src/DidHelpers/index.spec.ts index 7fa6af607..c1bef4ec4 100644 --- a/packages/sdk-js/src/DidHelpers/index.spec.ts +++ b/packages/sdk-js/src/DidHelpers/index.spec.ts @@ -6,11 +6,9 @@ */ import { SubmittableResult } from '@polkadot/api' - import { authorizeTx, resolver } from '@kiltprotocol/did' import type { DidDocument, KiltKeyringPair } from '@kiltprotocol/types' import { Crypto } from '@kiltprotocol/utils' - import { ConfigService } from '../index.js' import { ApiMocks, @@ -18,6 +16,8 @@ import { } from '../../../../tests/testUtils/index.js' import { transact } from './index.js' import { TransactionResult } from './interfaces.js' +import { createDid } from './createDid.js' +import { makeAttestationCreatedEvents } from '../../../../tests/testUtils/testData.js' jest.mock('@kiltprotocol/did', () => { return { @@ -34,9 +34,6 @@ describe('transact', () => { let keypair: KiltKeyringPair beforeAll(async () => { ConfigService.set({ api: mockedApi }) - // jest - // .mocked(mockedApi.derive.tx.events) - // .mockResolvedValue(mockedApi.createType('')) keypair = Crypto.makeKeypairFromUri('//Alice') const { id, verificationMethod, authentication } = @@ -72,7 +69,7 @@ describe('transact', () => { } throw new Error() }) - }) + }, 40000) it('creates a tx and checks status', async () => { const { txHex, checkResult } = await transact({ @@ -93,8 +90,8 @@ describe('transact', () => { expect(txHex).toContain('0x') const parsed = mockedApi.tx(txHex) expect(parsed.method).toMatchObject({ - section: 'did', - method: 'submitDidCall', + section: 'attestation', + method: 'add', }) await expect( @@ -105,12 +102,7 @@ describe('transact', () => { inBlock: new Uint8Array(32).fill(2), }), txHash: parsed.hash, - events: [ - mockedApi.createType('EventRecord', { - phase: { ApplyExtrinsic: 0 }, - event: { section: 'attestation', method: 'AttestationCreated' }, - }), - ], + events: makeAttestationCreatedEvents([[]]), }) ) ).resolves.toMatchObject>({ @@ -126,4 +118,49 @@ describe('transact', () => { }), }) }) + + it('create DID', async () => { + const { txHex, checkResult } = await ( + await createDid({ + api: mockedApi, + submitter: keypair, + signers: [keypair], + fromPublicKey: keypair, + }) + ).getSubmittable({ signSubmittable: false }) + + expect(txHex).toContain('0x') + const parsed = mockedApi.tx(txHex) + + const result = await checkResult( + new SubmittableResult({ + blockNumber: mockedApi.createType('BlockNumber', 1000), + status: mockedApi.createType('ExtrinsicStatus', { + inBlock: new Uint8Array(32).fill(2), + }), + txHash: parsed.hash, + events: [ + { + event: { + method: 'didCreated', + section: 'did', + }, + } as any, + ], + }) + ) + + expect(result).toMatchObject>({ + status: 'confirmed', + asConfirmed: expect.objectContaining({ + txHash: parsed.hash.toHex(), + block: { + hash: mockedApi + .createType('Hash', new Uint8Array(32).fill(2)) + .toHex(), + number: 1000n, + }, + }), + }) + }) }) diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index 4a3af9ebd..e4dd0d50a 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -36,6 +36,8 @@ import type { TransactionResult, } from './interfaces.js' +export { createDid } from './createDid.js' + /** * Selects and returns a DID signer for a given purpose and algorithm. * @@ -95,7 +97,7 @@ async function checkResult( switch (result.status.type) { case 'Finalized': case 'InBlock': - status = "confirmed" + // status = 'confirmed' // this is the block hash for both blockHash = result.status.value.toHex() if ('blockNumber' in result) { @@ -164,14 +166,20 @@ async function checkResult( error = mapError(event.data[1], api) } }) - const isSuccess = - !error && - expectedEvents.every(({ section, method }) => - txEvents.some( - ({ event }) => event.section === section && event.method === method - ) + const eventMatch = expectedEvents.every(({ section, method }) => + txEvents.some( + ({ event }) => + event.section.toLowerCase() === section.toLowerCase() && + event.method.toLowerCase() === method.toLowerCase() ) + ) + + const isSuccess = !error && eventMatch + status = isSuccess ? 'confirmed' : 'failed' + if (!isSuccess && !error) { + error = new Error('did not find expected events') + } } const { didDocument } = await DidResolver.resolve(did) @@ -240,7 +248,7 @@ async function checkResult( */ export function transact( options: SharedArguments & { - call: Extrinsic + call: Extrinsic | SubmittableExtrinsic expectedEvents: Array<{ section: string; method: string }> } ): TransactionHandlers { @@ -258,12 +266,19 @@ export function transact( options.didDocument, ...options.signers ) - const authorized: SubmittableExtrinsic = await authorizeTx( - options.didDocument, - options.call, - didSigners, - submitterAccount - ) + + let authorized: SubmittableExtrinsic + + if (!('send' in options.call)) { + authorized = await authorizeTx( + options.didDocument, + options.call, + didSigners, + submitterAccount + ) + } else { + authorized = options.call as SubmittableExtrinsic + } const result = await Blockchain.signAndSubmitTx( authorized, @@ -297,12 +312,19 @@ export function transact( options.didDocument, ...options.signers ) - const authorized: SubmittableExtrinsic = await authorizeTx( - options.didDocument, - options.call, - didSigners, - submitterAccount - ) + + let authorized: SubmittableExtrinsic + + if (!('send' in options.call)) { + authorized = await authorizeTx( + options.didDocument, + options.call, + didSigners, + submitterAccount + ) + } else { + authorized = options.call + } let signedHex const { signSubmittable = true } = submitOptions diff --git a/packages/sdk-js/src/DidHelpers/interfaces.ts b/packages/sdk-js/src/DidHelpers/interfaces.ts index 4d1fe380b..2bd0c6a6d 100644 --- a/packages/sdk-js/src/DidHelpers/interfaces.ts +++ b/packages/sdk-js/src/DidHelpers/interfaces.ts @@ -96,7 +96,7 @@ export interface TransactionHandlers { /** Base58 encoded bytes, using the bitcoin alphabet. */ type Base58Btc = string /** Multibase encoding of a public- or private key including multicodec variant flag. */ -type KeyMultibaseEncoded = `z${Base58Btc}` +export type KeyMultibaseEncoded = `z${Base58Btc}` export type AcceptedSigners = | SignerInterface diff --git a/packages/sdk-js/src/index.ts b/packages/sdk-js/src/index.ts index c1ef1bff2..ae04fcda5 100644 --- a/packages/sdk-js/src/index.ts +++ b/packages/sdk-js/src/index.ts @@ -19,6 +19,7 @@ import { Blockchain, } from '@kiltprotocol/chain-helpers' import { resolver as DidResolver } from '@kiltprotocol/did' +import * as DidHelpers from './DidHelpers/index.js' const { signAndSubmitTx } = Blockchain // TODO: maybe we don't even need that if we have the identity class const { signerFromKeypair } = Signers @@ -34,4 +35,5 @@ export { signerFromKeypair, signAndSubmitTx, ConfigService, + DidHelpers } diff --git a/tests/integration/didHelpers.spec.ts b/tests/integration/didHelpers.spec.ts new file mode 100644 index 000000000..1291700e2 --- /dev/null +++ b/tests/integration/didHelpers.spec.ts @@ -0,0 +1,62 @@ +/** + * 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 { disconnect } from '@kiltprotocol/sdk-js' +import * as SDK from '@kiltprotocol/sdk-js' +import { KiltKeyringPair } from '@kiltprotocol/types' +import { Crypto } from '@kiltprotocol/utils' +import type { ApiPromise } from '@polkadot/api' +import { + AcceptedPublicKeyEncodings, + SharedArguments, +} from 'sdk-js/src/DidHelpers/interfaces' +import { createEndowedTestAccount, initializeApi } from './utils' + +// Create did on chain +describe('createDid', () => { + let paymentAccount: KiltKeyringPair + let api: ApiPromise + + beforeAll(async () => { + api = await initializeApi() + }, 30_000) + + beforeAll(async () => { + paymentAccount = await createEndowedTestAccount() + }, 30_000) + + it('works', async () => { + const kp = Crypto.makeKeypairFromUri( + 'build hill second flame trigger simple rigid cabbage phrase evolve final eight', + 'sr25519' + ) + console.log(kp.address) + const options: Omit & { + fromPublicKey: AcceptedPublicKeyEncodings + } = { + api, + signers: [kp], + submitter: paymentAccount, + fromPublicKey: kp, + } + + // const did = await (await createDid(options)).submit() + const did = await SDK.DidHelpers.createDid(options) + const did2 = await did.submit() + + expect(did2.status).toBe('confirmed') + expect(did2.asConfirmed.didDocument).toMatchObject({ + id: `did:kilt:${kp.address}`, + }) + + // const did2 = await did.getSubmittable({ signSubmittable: false }) + }, 60000) + + afterAll(async () => { + disconnect() + }) +}) From 36b0cb55fdb54e2a5a9e12c2d5001c99176a420c Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Mon, 15 Jul 2024 17:38:23 +0200 Subject: [PATCH 09/70] chore: add didNonce parameter --- packages/sdk-js/src/DidHelpers/index.ts | 10 ++++++---- packages/sdk-js/src/DidHelpers/interfaces.ts | 7 ++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index e4dd0d50a..8ed5d5cc8 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -260,7 +260,7 @@ export function transact( const submit: TransactionHandlers['submit'] = async ({ awaitFinalized = true, - timeout = 0, + didNonce, } = {}) => { const didSigners = await signersForDid( options.didDocument, @@ -274,7 +274,8 @@ export function transact( options.didDocument, options.call, didSigners, - submitterAccount + submitterAccount, + { txCounter: options.api.createType('u64', didNonce) } ) } else { authorized = options.call as SubmittableExtrinsic @@ -288,7 +289,6 @@ export function transact( ? (res) => res.isFinalized || res.isError : (res) => res.isInBlock || res.isError, rejectOn: () => false, - timeout, } ) @@ -305,6 +305,7 @@ export function transact( submitOptions: | { signSubmittable?: boolean // default: true + didNonce?: number | BigInt } | undefined = {} ) => { @@ -320,7 +321,8 @@ export function transact( options.didDocument, options.call, didSigners, - submitterAccount + submitterAccount, + { txCounter: options.api.createType('u64', submitOptions.didNonce) } ) } else { authorized = options.call diff --git a/packages/sdk-js/src/DidHelpers/interfaces.ts b/packages/sdk-js/src/DidHelpers/interfaces.ts index 2bd0c6a6d..42d7cbcb9 100644 --- a/packages/sdk-js/src/DidHelpers/interfaces.ts +++ b/packages/sdk-js/src/DidHelpers/interfaces.ts @@ -58,12 +58,12 @@ export interface TransactionHandlers { * * @param options Options map to allow for named arguments. * @param options.awaitFinalized If set to true, this waits for finalization of the block of inclusion before returning. - * @param options.submitterAccount Sets or overrides the account which produces the outer signature on the transaction and thus covers submission fees. + * @param options.didNonce Allows explicitly setting the nonce to be used in DID authorization. * @returns A Promise resolving to the DID document and info on the success of the transaction. */ submit(options?: { awaitFinalized?: boolean // default: false - timeout?: number // in seconds + didNonce?: number | BigInt }): Promise /** * Produces a transaction that can be submitted to a blockchain node for inclusion, or signed and submitted by an external service. @@ -71,11 +71,12 @@ export interface TransactionHandlers { * @param options Options map to allow for named arguments. * @param options.signSubmittable If set to true, this signs the transaction with the submitterAccount, which covers transaction fees. * In this case, if no signer is available for this account, the function throws. - * @param options.submitterAccount Sets or overrides the account which produces the outer signature on the transaction and thus covers submission fees. + * @param options.didNonce Allows explicitly setting the nonce to be used in DID authorization. * @returns A Promise resolving to an Extrinsic object (encoded transaction). */ getSubmittable(options?: { signSubmittable?: boolean // default: true + didNonce?: number | BigInt }): Promise<{ txHex: HexString /** From bb7fe330e388e0d4a54e546e90847c0cd9632a99 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Mon, 15 Jul 2024 17:52:14 +0200 Subject: [PATCH 10/70] refactor: deduplicate transact logic --- packages/sdk-js/src/DidHelpers/index.ts | 68 ++++++++----------------- 1 file changed, 22 insertions(+), 46 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index 8ed5d5cc8..506fa56c3 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -14,10 +14,11 @@ import { FrameSystemEventRecord as EventRecord, SpRuntimeDispatchError, } from '@kiltprotocol/augment-api' +import { Blockchain } from '@kiltprotocol/chain-helpers' import { authorizeTx, - signersForDid, resolver as DidResolver, + signersForDid, } from '@kiltprotocol/did' import { KiltAddress, @@ -28,7 +29,6 @@ import { type SubmittableExtrinsic, } from '@kiltprotocol/types' import { Signers } from '@kiltprotocol/utils' -import { Blockchain } from '@kiltprotocol/chain-helpers' import type { SharedArguments, @@ -97,7 +97,7 @@ async function checkResult( switch (result.status.type) { case 'Finalized': case 'InBlock': - // status = 'confirmed' + // status must not be set here; this condition triggers a branch below // this is the block hash for both blockHash = result.status.value.toHex() if ('blockNumber' in result) { @@ -258,49 +258,6 @@ export function transact( : options.submitter.id ) as KiltAddress - const submit: TransactionHandlers['submit'] = async ({ - awaitFinalized = true, - didNonce, - } = {}) => { - const didSigners = await signersForDid( - options.didDocument, - ...options.signers - ) - - let authorized: SubmittableExtrinsic - - if (!('send' in options.call)) { - authorized = await authorizeTx( - options.didDocument, - options.call, - didSigners, - submitterAccount, - { txCounter: options.api.createType('u64', didNonce) } - ) - } else { - authorized = options.call as SubmittableExtrinsic - } - - const result = await Blockchain.signAndSubmitTx( - authorized, - options.submitter, - { - resolveOn: awaitFinalized - ? (res) => res.isFinalized || res.isError - : (res) => res.isInBlock || res.isError, - rejectOn: () => false, - } - ) - - return checkResult( - result, - options.api, - options.expectedEvents, - options.didDocument.id, - didSigners - ) - } - const getSubmittable: TransactionHandlers['getSubmittable'] = async ( submitOptions: | { @@ -355,6 +312,25 @@ export function transact( } } + const submit: TransactionHandlers['submit'] = async ({ + awaitFinalized = true, + didNonce, + } = {}) => { + const submittable = await getSubmittable({ didNonce }) + + const result = await Blockchain.submitSignedTx( + options.api.tx(submittable.txHex), + { + resolveOn: awaitFinalized + ? (res) => res.isFinalized || res.isError + : (res) => res.isInBlock || res.isError, + rejectOn: () => false, + } + ) + + return submittable.checkResult(result) + } + return { submit, getSubmittable, From ab417f7780ae8c16c5ea1139c12ac1aa886e63be Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 16 Jul 2024 11:40:01 +0200 Subject: [PATCH 11/70] refactor: make createDid synchronous --- packages/did/src/DidDetails/index.ts | 2 +- packages/sdk-js/src/DidHelpers/createDid.ts | 2 +- packages/sdk-js/src/DidHelpers/index.spec.ts | 6 +- packages/sdk-js/src/DidHelpers/index.ts | 196 +++++++++++++------ packages/sdk-js/src/index.ts | 2 +- tests/integration/didHelpers.spec.ts | 7 +- 6 files changed, 148 insertions(+), 67 deletions(-) diff --git a/packages/did/src/DidDetails/index.ts b/packages/did/src/DidDetails/index.ts index ccdd0b106..8f61d85cf 100644 --- a/packages/did/src/DidDetails/index.ts +++ b/packages/did/src/DidDetails/index.ts @@ -19,7 +19,7 @@ export type { export { isValidDidVerificationType, isValidEncryptionMethodType, - signingMethodTypes + signingMethodTypes, } from './DidDetails.js' export * from './LightDidDetails.js' export * from './FullDidDetails.js' diff --git a/packages/sdk-js/src/DidHelpers/createDid.ts b/packages/sdk-js/src/DidHelpers/createDid.ts index c1a5f6718..26d76a02a 100644 --- a/packages/sdk-js/src/DidHelpers/createDid.ts +++ b/packages/sdk-js/src/DidHelpers/createDid.ts @@ -28,7 +28,7 @@ import type { } from './interfaces.js' import { transact } from './index.js' -function convertPublicKey(pk: AcceptedPublicKeyEncodings): { +export function convertPublicKey(pk: AcceptedPublicKeyEncodings): { publicKey: Uint8Array keyType: string } { diff --git a/packages/sdk-js/src/DidHelpers/index.spec.ts b/packages/sdk-js/src/DidHelpers/index.spec.ts index c1bef4ec4..6e768e431 100644 --- a/packages/sdk-js/src/DidHelpers/index.spec.ts +++ b/packages/sdk-js/src/DidHelpers/index.spec.ts @@ -89,10 +89,8 @@ describe('transact', () => { expect(txHex).toContain('0x') const parsed = mockedApi.tx(txHex) - expect(parsed.method).toMatchObject({ - section: 'attestation', - method: 'add', - }) + expect(parsed.method).toHaveProperty('section', 'did') + expect(parsed.method).toHaveProperty('method', 'submitDidCall') await expect( checkResult( diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index 506fa56c3..a3ff0d796 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -18,7 +18,10 @@ import { Blockchain } from '@kiltprotocol/chain-helpers' import { authorizeTx, resolver as DidResolver, + getFullDid, + getStoreTx, signersForDid, + signingMethodTypes, } from '@kiltprotocol/did' import { KiltAddress, @@ -28,16 +31,16 @@ import { type SignerInterface, type SubmittableExtrinsic, } from '@kiltprotocol/types' -import { Signers } from '@kiltprotocol/utils' +import { Crypto, Signers } from '@kiltprotocol/utils' +import { convertPublicKey } from './createDid.js' import type { + AcceptedPublicKeyEncodings, SharedArguments, TransactionHandlers, TransactionResult, } from './interfaces.js' -export { createDid } from './createDid.js' - /** * Selects and returns a DID signer for a given purpose and algorithm. * @@ -80,12 +83,12 @@ function mapError(err: SpRuntimeDispatchError, api: ApiPromise): Error { return new Error(`${err.type}: ${err.value.toHuman()}`) } -async function checkResult( +async function checkResultImpl( result: { blockHash: HexString; txHash: HexString } | SubmittableResultValue, api: ApiPromise, expectedEvents: Array<{ section: string; method: string }>, did: Did, - signers: SignerInterface[] + signersOrKeys: SharedArguments['signers'] ): Promise { let txEvents: EventRecord[] = [] let status: TransactionResult['status'] | undefined @@ -188,7 +191,10 @@ async function checkResult( await (await api.at(blockHash)).query.system.number() ).toBigInt() } - + let signers: Awaited> + if (didDocument) { + signers = await signersForDid(didDocument, ...signersOrKeys) + } return { status, get asFailed(): TransactionResult['asFailed'] { @@ -239,6 +245,29 @@ async function checkResult( } } +async function submitImpl( + getSubmittable: TransactionHandlers['getSubmittable'], + options: Pick & { + didNonce?: number | BigInt + awaitFinalized?: boolean + } +): ReturnType { + const submittable = await getSubmittable(options) + + const { awaitFinalized = true } = options + const result = await Blockchain.submitSignedTx( + options.api.tx(submittable.txHex), + { + resolveOn: awaitFinalized + ? (res) => res.isFinalized || res.isError + : (res) => res.isInBlock || res.isError, + rejectOn: () => false, + } + ) + + return submittable.checkResult(result) +} + /** * Instructs a transaction (state transition) as this DID (with this DID as the origin). * @@ -252,12 +281,6 @@ export function transact( expectedEvents: Array<{ section: string; method: string }> } ): TransactionHandlers { - const submitterAccount = ( - 'address' in options.submitter - ? options.submitter.address - : options.submitter.id - ) as KiltAddress - const getSubmittable: TransactionHandlers['getSubmittable'] = async ( submitOptions: | { @@ -266,33 +289,34 @@ export function transact( } | undefined = {} ) => { - const didSigners = await signersForDid( - options.didDocument, - ...options.signers - ) + const { didDocument, signers, submitter, call, api, expectedEvents } = + options + const { didNonce, signSubmittable = true } = submitOptions + const didSigners = await signersForDid(didDocument, ...signers) - let authorized: SubmittableExtrinsic + const submitterAccount = ( + 'address' in submitter ? submitter.address : submitter.id + ) as KiltAddress - if (!('send' in options.call)) { - authorized = await authorizeTx( - options.didDocument, - options.call, - didSigners, - submitterAccount, - { txCounter: options.api.createType('u64', submitOptions.didNonce) } - ) - } else { - authorized = options.call - } + const authorized: SubmittableExtrinsic = await authorizeTx( + didDocument, + call, + didSigners, + submitterAccount, + typeof didNonce !== 'undefined' + ? { + txCounter: api.createType('u64', didNonce), + } + : {} + ) let signedHex - const { signSubmittable = true } = submitOptions if (signSubmittable) { const signed = - 'address' in options.submitter - ? await authorized.signAsync(options.submitter) + 'address' in submitter + ? await authorized.signAsync(submitter) : await authorized.signAsync(submitterAccount, { - signer: Signers.getPolkadotSigner([options.submitter]), + signer: Signers.getPolkadotSigner([submitter]), }) signedHex = signed.toHex() } else { @@ -302,37 +326,97 @@ export function transact( return { txHex: signedHex, checkResult: (input) => - checkResult( - input, - options.api, - options.expectedEvents, - options.didDocument.id, - didSigners - ), + checkResultImpl(input, api, expectedEvents, didDocument.id, signers), } } - const submit: TransactionHandlers['submit'] = async ({ - awaitFinalized = true, - didNonce, - } = {}) => { - const submittable = await getSubmittable({ didNonce }) + const submit: TransactionHandlers['submit'] = (submitOptions) => + submitImpl(getSubmittable, { ...options, ...submitOptions }) - const result = await Blockchain.submitSignedTx( - options.api.tx(submittable.txHex), + return { + submit, + getSubmittable, + } +} + +/** + * Creates an on-chain DID based on an authentication key. + * + * @param options.fromPublicKey The public key that will feature as the DID's initial authentication method and will determine the DID identifier. + * @param options + */ +export function createDid( + options: Omit & { + fromPublicKey: AcceptedPublicKeyEncodings + } +): TransactionHandlers { + function implementsSignerInterface(input: any): input is SignerInterface { + return 'algorithm' in input && 'id' in input && 'sign' in input + } + + const getSubmittable: TransactionHandlers['getSubmittable'] = async ( + submitOptions = {} + ) => { + const { fromPublicKey, submitter, signers, api } = options + const { signSubmittable = true } = submitOptions + const { publicKey, keyType } = convertPublicKey(fromPublicKey) + + if (!signingMethodTypes.includes(keyType)) { + throw new Error('invalid public key') + } + const submitterAccount = ( + 'address' in submitter ? submitter.address : submitter.id + ) as KiltAddress + + const accountSigners = ( + await Promise.all( + signers.map(async (signer) => { + if (implementsSignerInterface(signer)) { + return [signer] + } + const res = await Signers.getSignersForKeypair({ + keypair: signer, + }) + return res + }) + ) + ).flat() + const didCreation = await getStoreTx( { - resolveOn: awaitFinalized - ? (res) => res.isFinalized || res.isError - : (res) => res.isInBlock || res.isError, - rejectOn: () => false, - } + authentication: [{ publicKey, type: keyType as 'sr25519' }], + }, + submitterAccount, + accountSigners ) - return submittable.checkResult(result) - } + 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() + } - return { - submit, - getSubmittable, + return { + txHex: signedHex, + checkResult: (input) => + checkResultImpl( + input, + api, + [{ section: 'did', method: 'DidCreated' }], + getFullDid(Crypto.encodeAddress(publicKey, 38)), + signers + ), + } } + + const submit: TransactionHandlers['submit'] = (submitOptions) => + submitImpl(getSubmittable, { ...options, ...submitOptions }) + + return { getSubmittable, submit } } diff --git a/packages/sdk-js/src/index.ts b/packages/sdk-js/src/index.ts index ae04fcda5..cef20b3a2 100644 --- a/packages/sdk-js/src/index.ts +++ b/packages/sdk-js/src/index.ts @@ -35,5 +35,5 @@ export { signerFromKeypair, signAndSubmitTx, ConfigService, - DidHelpers + DidHelpers, } diff --git a/tests/integration/didHelpers.spec.ts b/tests/integration/didHelpers.spec.ts index 1291700e2..0d92fcc16 100644 --- a/tests/integration/didHelpers.spec.ts +++ b/tests/integration/didHelpers.spec.ts @@ -45,11 +45,10 @@ describe('createDid', () => { } // const did = await (await createDid(options)).submit() - const did = await SDK.DidHelpers.createDid(options) - const did2 = await did.submit() + const result = await SDK.DidHelpers.createDid(options).submit() - expect(did2.status).toBe('confirmed') - expect(did2.asConfirmed.didDocument).toMatchObject({ + expect(result.status).toBe('confirmed') + expect(result.asConfirmed.didDocument).toMatchObject({ id: `did:kilt:${kp.address}`, }) From d91af9485737492f60c2dd01d6e09e0f78feb651 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 16 Jul 2024 13:33:33 +0200 Subject: [PATCH 12/70] feat: draft set verification method --- .../src/DidHelpers/setVerificationMethod.ts | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 packages/sdk-js/src/DidHelpers/setVerificationMethod.ts diff --git a/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts b/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts new file mode 100644 index 000000000..7b979a2d6 --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts @@ -0,0 +1,83 @@ +/** + * 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 { + NewDidEncryptionKey, + NewDidVerificationKey, + publicKeyToChain, +} from '@kiltprotocol/did' +import { VerificationRelationship } from '@kiltprotocol/types' +import { convertPublicKey } from './createDid.js' +import { transact } from './index.js' +import type { + AcceptedPublicKeyEncodings, + SharedArguments, + TransactionHandlers, +} from './interfaces' + +/** + * Replaces all existing verification methods for the selected `relationship` with `publicKey`. + * + * @param options.publicKey The public key to be used for this verification method. + * @param options.relationship The relationship for which this verification method shall be useable. + * @param options + */ +export function setVerificationMethod( + options: SharedArguments & { + publicKey: AcceptedPublicKeyEncodings + relationship: VerificationRelationship + } +): TransactionHandlers { + const pk = convertPublicKey(options.publicKey) + let didKeyUpdateTx + + if (options.relationship === 'keyAgreement') { + // TODO: check if types of keys are valid? + const didAuthKey: NewDidEncryptionKey = { + publicKey: pk.publicKey, + type: pk.keyType as any, + } + didKeyUpdateTx = options.api.tx.did.addKeyAgreementKey( + publicKeyToChain(didAuthKey) + ) + } else { + const didAuthKey: NewDidVerificationKey = { + publicKey: pk.publicKey, + type: pk.keyType as any, + } + + switch (options.relationship) { + case 'authentication': { + didKeyUpdateTx = options.api.tx.did.setAuthenticationKey( + publicKeyToChain(didAuthKey) + ) + break + } + case 'capabilityDelegation': { + didKeyUpdateTx = options.api.tx.did.setDelegationKey( + publicKeyToChain(didAuthKey) + ) + break + } + case 'assertionMethod': { + didKeyUpdateTx = options.api.tx.did.assertionMethod( + publicKeyToChain(didAuthKey) + ) + break + } + default: { + throw new Error('unsupported relationship') + } + } + } + + return transact({ + ...options, + call: didKeyUpdateTx, + expectedEvents: [{ section: 'did', method: 'DidUpdated' }], + }) +} From c1c616996e9305b7bd21cfb0a09c7ee69685ba14 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 16 Jul 2024 13:46:25 +0200 Subject: [PATCH 13/70] refactor: reorganize code --- packages/sdk-js/src/DidHelpers/common.ts | 255 ++++++++++++ packages/sdk-js/src/DidHelpers/createDid.ts | 173 +++----- packages/sdk-js/src/DidHelpers/index.ts | 386 +----------------- .../src/DidHelpers/setVerificationMethod.ts | 13 +- packages/sdk-js/src/DidHelpers/transact.ts | 86 ++++ 5 files changed, 417 insertions(+), 496 deletions(-) create mode 100644 packages/sdk-js/src/DidHelpers/common.ts create mode 100644 packages/sdk-js/src/DidHelpers/transact.ts diff --git a/packages/sdk-js/src/DidHelpers/common.ts b/packages/sdk-js/src/DidHelpers/common.ts new file mode 100644 index 000000000..21887d043 --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/common.ts @@ -0,0 +1,255 @@ +/** + * 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. + */ + +/* eslint-disable jsdoc/require-jsdoc */ +// functions in this file are not meant to be public + +import type { ApiPromise } from '@polkadot/api' +import type { SubmittableResultValue } from '@polkadot/api/types' +import type { BlockNumber } from '@polkadot/types/interfaces' +import { u8aToHex, u8aToU8a } from '@polkadot/util' + +import type { + FrameSystemEventRecord as EventRecord, + SpRuntimeDispatchError, +} from '@kiltprotocol/augment-api' +import { Blockchain } from '@kiltprotocol/chain-helpers' +import { + resolver as DidResolver, + multibaseKeyToDidKey, + signersForDid, +} from '@kiltprotocol/did' +import type { Did, HexString } from '@kiltprotocol/types' + +import type { + AcceptedPublicKeyEncodings, + KeyMultibaseEncoded, + SharedArguments, + TransactionHandlers, + TransactionResult, +} from './interfaces.js' + +function mapError(err: SpRuntimeDispatchError, api: ApiPromise): Error { + if (err.isModule) { + const { docs, method, section } = api.findError(err.asModule.index.toU8a()) + return new Error(`${section}.${method}: ${docs}`) + } + return new Error(`${err.type}: ${err.value.toHuman()}`) +} + +export async function checkResultImpl( + result: { blockHash: HexString; txHash: HexString } | SubmittableResultValue, + api: ApiPromise, + expectedEvents: Array<{ section: string; method: string }> = [], + did: Did, + signersOrKeys: SharedArguments['signers'] +): Promise { + let txEvents: EventRecord[] = [] + let status: TransactionResult['status'] | undefined + let error: Error | undefined + let blockHash: HexString | undefined + let blockNumber: BigInt | undefined + if ('status' in result) { + txEvents = result.events ?? [] + switch (result.status.type) { + case 'Finalized': + case 'InBlock': + // status must not be set here; this condition triggers a branch below + // this is the block hash for both + blockHash = result.status.value.toHex() + if ('blockNumber' in result) { + blockNumber = (result.blockNumber as BlockNumber).toBigInt() + } + break + case 'Dropped': + case 'FinalityTimeout': + case 'Invalid': + case 'Usurped': + status = 'rejected' + error = new Error(result.status.type) + break + case 'Broadcast': + case 'Future': + case 'Ready': + case 'Retracted': + status = 'unknown' + error = new Error(result.status.type) + break + default: + status = 'unknown' + error = new Error(`unknown tx status variant ${result.status.type}`) + } + } else if ( + 'blockHash' in result && + 'txHash' in result && + typeof result.blockHash === 'string' && + typeof result.txHash === 'string' + ) { + const txHashHash = api.createType('Hash', result.blockHash) + const { + block: { block }, + events, + } = await api.derive.tx.events(txHashHash) + blockNumber = block.header.number.toBigInt() + blockHash = result.blockHash + const txIndex = block.extrinsics.findIndex((tx) => + tx.hash.eq(result.txHash) + ) + txEvents = events.filter( + ({ phase }) => + phase.isApplyExtrinsic && phase.asApplyExtrinsic.eqn(txIndex) + ) + } else { + status = 'unknown' + error = new Error('missing blockHash and/or txHash') + } + if (typeof status !== 'string') { + txEvents.forEach(({ event }) => { + if (api.events.system.ExtrinsicFailed.is(event)) { + error = mapError(event.data[0], api) + } else if (api.events.proxy.ProxyExecuted.is(event)) { + const res = event.data[0] + if (res.isErr) { + error = mapError(res.asErr, api) + } + } else if (api.events.did.DidCallDispatched.is(event)) { + const res = event.data[1] + if (res.isErr) { + error = mapError(res.asErr, api) + } + } else if (api.events.utility.ItemFailed.is(event)) { + error = mapError(event.data[0], api) + } else if (api.events.utility.BatchInterrupted.is(event)) { + error = mapError(event.data[1], api) + } + }) + const eventMatch = expectedEvents.every(({ section, method }) => + txEvents.some( + ({ event }) => + event.section.toLowerCase() === section.toLowerCase() && + event.method.toLowerCase() === method.toLowerCase() + ) + ) + + const isSuccess = !error && eventMatch + + status = isSuccess ? 'confirmed' : 'failed' + if (!isSuccess && !error) { + error = new Error('did not find expected events') + } + } + + const { didDocument } = await DidResolver.resolve(did) + if (blockHash && !blockNumber) { + blockNumber = ( + await (await api.at(blockHash)).query.system.number() + ).toBigInt() + } + let signers: Awaited> + if (didDocument) { + signers = await signersForDid(didDocument, ...signersOrKeys) + } + return { + status, + get asFailed(): TransactionResult['asFailed'] { + if (status !== 'failed') { + throw new Error('') + } + return { + error: error!, + txHash: u8aToHex(u8aToU8a(result.txHash)), + signers, + didDocument, + block: { hash: blockHash!, number: blockNumber! }, + events: txEvents.map(({ event }) => event), + } + }, + get asUnknown(): TransactionResult['asUnknown'] { + if (status !== 'unknown') { + throw new Error('') + } + return { + error: error as Error, + txHash: u8aToHex(u8aToU8a(result.txHash)), + } + }, + get asRejected(): TransactionResult['asRejected'] { + if (status !== 'rejected') { + throw new Error('') + } + return { + error: error as Error, + txHash: u8aToHex(u8aToU8a(result.txHash)), + signers, + didDocument, + } + }, + get asConfirmed(): TransactionResult['asConfirmed'] { + if (status !== 'confirmed') { + throw new Error('') + } + return { + txHash: u8aToHex(u8aToU8a(result.txHash)), + signers, + didDocument: didDocument!, + block: { hash: blockHash!, number: blockNumber! }, + events: txEvents.map(({ event }) => event), + } + }, + } +} + +export async function submitImpl( + getSubmittable: TransactionHandlers['getSubmittable'], + options: Pick & { + didNonce?: number | BigInt + awaitFinalized?: boolean + } +): ReturnType { + const submittable = await getSubmittable(options) + + const { awaitFinalized = true } = options + const result = await Blockchain.submitSignedTx( + options.api.tx(submittable.txHex), + { + resolveOn: awaitFinalized + ? (res) => res.isFinalized || res.isError + : (res) => res.isInBlock || res.isError, + rejectOn: () => false, + } + ) + + return submittable.checkResult(result) +} + +export function convertPublicKey(pk: AcceptedPublicKeyEncodings): { + publicKey: Uint8Array + keyType: string +} { + let didPublicKey: { + publicKey: Uint8Array + keyType: string + } + if (typeof pk === 'string') { + didPublicKey = multibaseKeyToDidKey(pk) + } else if ('publicKeyMultibase' in pk) { + didPublicKey = multibaseKeyToDidKey( + (pk as { publicKeyMultibase: KeyMultibaseEncoded }).publicKeyMultibase + ) + } else if ( + 'publicKey' in pk && + pk.publicKey.constructor.name === 'Uint8Array' + ) { + didPublicKey = { + publicKey: pk.publicKey, + keyType: pk.type, + } + } else { + throw new Error('invalid public key') + } + return didPublicKey +} diff --git a/packages/sdk-js/src/DidHelpers/createDid.ts b/packages/sdk-js/src/DidHelpers/createDid.ts index 26d76a02a..5a7d6b826 100644 --- a/packages/sdk-js/src/DidHelpers/createDid.ts +++ b/packages/sdk-js/src/DidHelpers/createDid.ts @@ -5,55 +5,19 @@ * found in the LICENSE file in the root directory of this source tree. */ -import { - DidVerificationMethodType, - getStoreTx, - keypairToMultibaseKey, - multibaseKeyToDidKey, - signingMethodTypes, -} from '@kiltprotocol/did' -import { KiltAddress, KeyringPair, SignerInterface } from '@kiltprotocol/types' -import * as Did from '@kiltprotocol/did' -import type { - DidDocument, - DidUrl, - VerificationMethod, -} from '@kiltprotocol/types' -import { Signers } from '@kiltprotocol/utils' +import { getFullDid, getStoreTx, signingMethodTypes } from '@kiltprotocol/did' +import type { KiltAddress, SignerInterface } from '@kiltprotocol/types' +import { Crypto, Signers } from '@kiltprotocol/utils' + +import { checkResultImpl, convertPublicKey, submitImpl } from './common.js' import type { AcceptedPublicKeyEncodings, - KeyMultibaseEncoded, SharedArguments, TransactionHandlers, } from './interfaces.js' -import { transact } from './index.js' -export function convertPublicKey(pk: AcceptedPublicKeyEncodings): { - publicKey: Uint8Array - keyType: string -} { - let didPublicKey: { - publicKey: Uint8Array - keyType: string - } - if (typeof pk === 'string') { - didPublicKey = multibaseKeyToDidKey(pk) - } else if ('publicKeyMultibase' in pk) { - didPublicKey = multibaseKeyToDidKey( - (pk as { publicKeyMultibase: KeyMultibaseEncoded }).publicKeyMultibase - ) - } else if ( - 'publicKey' in pk && - pk.publicKey.constructor.name === 'Uint8Array' - ) { - didPublicKey = { - publicKey: pk.publicKey, - keyType: pk.type, - } - } else { - throw new Error('invalid public key') - } - return didPublicKey +function implementsSignerInterface(input: any): input is SignerInterface { + return 'algorithm' in input && 'id' in input && 'sign' in input } /** @@ -62,87 +26,74 @@ export function convertPublicKey(pk: AcceptedPublicKeyEncodings): { * @param options.fromPublicKey The public key that will feature as the DID's initial authentication method and will determine the DID identifier. * @param options */ -export async function createDid( +export function createDid( options: Omit & { fromPublicKey: AcceptedPublicKeyEncodings } -): Promise { - const didPublicKey = convertPublicKey(options.fromPublicKey) - - if (!signingMethodTypes.includes(didPublicKey.keyType)) { - throw new Error('invalid public key') - } - - const didPublicKeyMultibase = keypairToMultibaseKey({ - publicKey: didPublicKey.publicKey, - type: didPublicKey.keyType as DidVerificationMethodType, - }) - - const submitterAccount = ( - 'address' in options.submitter - ? options.submitter.address - : options.submitter.id - ) as KiltAddress - - const didAuthKey: Did.NewDidVerificationKey = { - publicKey: didPublicKey.publicKey, - type: didPublicKey.keyType as any, - } +): TransactionHandlers { + const getSubmittable: TransactionHandlers['getSubmittable'] = async ( + submitOptions = {} + ) => { + const { fromPublicKey, submitter, signers, api } = options + const { signSubmittable = true } = submitOptions + const { publicKey, keyType } = convertPublicKey(fromPublicKey) - function implementsSignerInterface(input: any): boolean { - return 'algorithm' in input && 'id' in input && 'sign' in input - } + if (!signingMethodTypes.includes(keyType)) { + throw new Error('invalid public key') + } + const submitterAccount = ( + 'address' in submitter ? submitter.address : submitter.id + ) as KiltAddress - const signers: SignerInterface[] = ( - await Promise.all( - options.signers - .map(async (signer) => { + const accountSigners = ( + await Promise.all( + signers.map(async (signer) => { if (implementsSignerInterface(signer)) { - return signer as SignerInterface + return [signer] } - const res = (await Signers.getSignersForKeypair({ - keypair: signer as - | KeyringPair - | { - secretKeyMultibase: KeyMultibaseEncoded - publicKeyMultibase: KeyMultibaseEncoded - }, - })) as SignerInterface[] + const res = await Signers.getSignersForKeypair({ + keypair: signer, + }) return res }) - .flat() + ) + ).flat() + const didCreation = await getStoreTx( + { + authentication: [{ publicKey, type: keyType as 'sr25519' }], + }, + submitterAccount, + accountSigners ) - ).flat() - const fullDid = Did.getFullDidFromVerificationMethod({ - publicKeyMultibase: didPublicKeyMultibase, - }) - - const vm: VerificationMethod = { - id: `${fullDid}#auth` as DidUrl, - controller: fullDid, - type: 'Multikey', - publicKeyMultibase: didPublicKeyMultibase, - } + 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() + } - const didDocument: DidDocument = { - id: fullDid, - verificationMethod: [vm], - authentication: [vm.id], + return { + txHex: signedHex, + checkResult: (input) => + checkResultImpl( + input, + api, + [{ section: 'did', method: 'DidCreated' }], + getFullDid(Crypto.encodeAddress(publicKey, 38)), + signers + ), + } } - const fullDidCreationTx = await getStoreTx( - { - authentication: [didAuthKey], - }, - submitterAccount, - signers - ) + const submit: TransactionHandlers['submit'] = (submitOptions) => + submitImpl(getSubmittable, { ...options, ...submitOptions }) - return transact({ - ...options, - didDocument, - call: fullDidCreationTx, - expectedEvents: [{ section: 'did', method: 'DidCreated' }], - }) + return { getSubmittable, submit } } diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index a3ff0d796..de8295262 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -5,41 +5,15 @@ * found in the LICENSE file in the root directory of this source tree. */ -import type { ApiPromise } from '@polkadot/api' -import type { SubmittableResultValue } from '@polkadot/api/types' -import type { BlockNumber, Extrinsic } from '@polkadot/types/interfaces' -import { u8aToHex, u8aToU8a } from '@polkadot/util' +import { signersForDid } from '@kiltprotocol/did' +import type { DidUrl, SignerInterface } from '@kiltprotocol/types' +import { Signers } from '@kiltprotocol/utils' -import { - FrameSystemEventRecord as EventRecord, - SpRuntimeDispatchError, -} from '@kiltprotocol/augment-api' -import { Blockchain } from '@kiltprotocol/chain-helpers' -import { - authorizeTx, - resolver as DidResolver, - getFullDid, - getStoreTx, - signersForDid, - signingMethodTypes, -} from '@kiltprotocol/did' -import { - KiltAddress, - type Did, - type DidUrl, - type HexString, - type SignerInterface, - type SubmittableExtrinsic, -} from '@kiltprotocol/types' -import { Crypto, Signers } from '@kiltprotocol/utils' +import type { SharedArguments } from './interfaces.js' -import { convertPublicKey } from './createDid.js' -import type { - AcceptedPublicKeyEncodings, - SharedArguments, - TransactionHandlers, - TransactionResult, -} from './interfaces.js' +export { createDid } from './createDid.js' +export { setVerificationMethod } from './setVerificationMethod.js' +export { transact } from './transact.js' /** * Selects and returns a DID signer for a given purpose and algorithm. @@ -74,349 +48,3 @@ export async function selectSigner({ return Signers.selectSigner(mappedSigners, ...selectors) } - -function mapError(err: SpRuntimeDispatchError, api: ApiPromise): Error { - if (err.isModule) { - const { docs, method, section } = api.findError(err.asModule.index.toU8a()) - return new Error(`${section}.${method}: ${docs}`) - } - return new Error(`${err.type}: ${err.value.toHuman()}`) -} - -async function checkResultImpl( - result: { blockHash: HexString; txHash: HexString } | SubmittableResultValue, - api: ApiPromise, - expectedEvents: Array<{ section: string; method: string }>, - did: Did, - signersOrKeys: SharedArguments['signers'] -): Promise { - let txEvents: EventRecord[] = [] - let status: TransactionResult['status'] | undefined - let error: Error | undefined - let blockHash: HexString | undefined - let blockNumber: BigInt | undefined - if ('status' in result) { - txEvents = result.events ?? [] - switch (result.status.type) { - case 'Finalized': - case 'InBlock': - // status must not be set here; this condition triggers a branch below - // this is the block hash for both - blockHash = result.status.value.toHex() - if ('blockNumber' in result) { - blockNumber = (result.blockNumber as BlockNumber).toBigInt() - } - break - case 'Dropped': - case 'FinalityTimeout': - case 'Invalid': - case 'Usurped': - status = 'rejected' - error = new Error(result.status.type) - break - case 'Broadcast': - case 'Future': - case 'Ready': - case 'Retracted': - status = 'unknown' - error = new Error(result.status.type) - break - default: - status = 'unknown' - error = new Error(`unknown tx status variant ${result.status.type}`) - } - } else if ( - 'blockHash' in result && - 'txHash' in result && - typeof result.blockHash === 'string' && - typeof result.txHash === 'string' - ) { - const txHashHash = api.createType('Hash', result.blockHash) - const { - block: { block }, - events, - } = await api.derive.tx.events(txHashHash) - blockNumber = block.header.number.toBigInt() - blockHash = result.blockHash - const txIndex = block.extrinsics.findIndex((tx) => - tx.hash.eq(result.txHash) - ) - txEvents = events.filter( - ({ phase }) => - phase.isApplyExtrinsic && phase.asApplyExtrinsic.eqn(txIndex) - ) - } else { - status = 'unknown' - error = new Error('missing blockHash and/or txHash') - } - if (typeof status !== 'string') { - txEvents.forEach(({ event }) => { - if (api.events.system.ExtrinsicFailed.is(event)) { - error = mapError(event.data[0], api) - } else if (api.events.proxy.ProxyExecuted.is(event)) { - const res = event.data[0] - if (res.isErr) { - error = mapError(res.asErr, api) - } - } else if (api.events.did.DidCallDispatched.is(event)) { - const res = event.data[1] - if (res.isErr) { - error = mapError(res.asErr, api) - } - } else if (api.events.utility.ItemFailed.is(event)) { - error = mapError(event.data[0], api) - } else if (api.events.utility.BatchInterrupted.is(event)) { - error = mapError(event.data[1], api) - } - }) - const eventMatch = expectedEvents.every(({ section, method }) => - txEvents.some( - ({ event }) => - event.section.toLowerCase() === section.toLowerCase() && - event.method.toLowerCase() === method.toLowerCase() - ) - ) - - const isSuccess = !error && eventMatch - - status = isSuccess ? 'confirmed' : 'failed' - if (!isSuccess && !error) { - error = new Error('did not find expected events') - } - } - - const { didDocument } = await DidResolver.resolve(did) - if (blockHash && !blockNumber) { - blockNumber = ( - await (await api.at(blockHash)).query.system.number() - ).toBigInt() - } - let signers: Awaited> - if (didDocument) { - signers = await signersForDid(didDocument, ...signersOrKeys) - } - return { - status, - get asFailed(): TransactionResult['asFailed'] { - if (status !== 'failed') { - throw new Error('') - } - return { - error: error!, - txHash: u8aToHex(u8aToU8a(result.txHash)), - signers, - didDocument, - block: { hash: blockHash!, number: blockNumber! }, - events: txEvents.map(({ event }) => event), - } - }, - get asUnknown(): TransactionResult['asUnknown'] { - if (status !== 'unknown') { - throw new Error('') - } - return { - error: error as Error, - txHash: u8aToHex(u8aToU8a(result.txHash)), - } - }, - get asRejected(): TransactionResult['asRejected'] { - if (status !== 'rejected') { - throw new Error('') - } - return { - error: error as Error, - txHash: u8aToHex(u8aToU8a(result.txHash)), - signers, - didDocument, - } - }, - get asConfirmed(): TransactionResult['asConfirmed'] { - if (status !== 'confirmed') { - throw new Error('') - } - return { - txHash: u8aToHex(u8aToU8a(result.txHash)), - signers, - didDocument: didDocument!, - block: { hash: blockHash!, number: blockNumber! }, - events: txEvents.map(({ event }) => event), - } - }, - } -} - -async function submitImpl( - getSubmittable: TransactionHandlers['getSubmittable'], - options: Pick & { - didNonce?: number | BigInt - awaitFinalized?: boolean - } -): ReturnType { - const submittable = await getSubmittable(options) - - const { awaitFinalized = true } = options - const result = await Blockchain.submitSignedTx( - options.api.tx(submittable.txHex), - { - resolveOn: awaitFinalized - ? (res) => res.isFinalized || res.isError - : (res) => res.isInBlock || res.isError, - rejectOn: () => false, - } - ) - - return submittable.checkResult(result) -} - -/** - * Instructs a transaction (state transition) as this DID (with this DID as the origin). - * - * @param options - * @param options.call The transaction / call to execute. - * @returns - */ -export function transact( - options: SharedArguments & { - call: Extrinsic | SubmittableExtrinsic - expectedEvents: Array<{ section: string; method: string }> - } -): TransactionHandlers { - const getSubmittable: TransactionHandlers['getSubmittable'] = async ( - submitOptions: - | { - signSubmittable?: boolean // default: true - didNonce?: number | BigInt - } - | undefined = {} - ) => { - const { didDocument, signers, submitter, call, api, expectedEvents } = - options - const { didNonce, signSubmittable = true } = submitOptions - const didSigners = await signersForDid(didDocument, ...signers) - - const submitterAccount = ( - 'address' in submitter ? submitter.address : submitter.id - ) as KiltAddress - - const authorized: SubmittableExtrinsic = await authorizeTx( - didDocument, - call, - didSigners, - submitterAccount, - typeof didNonce !== 'undefined' - ? { - txCounter: api.createType('u64', didNonce), - } - : {} - ) - - 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() - } - - return { - txHex: signedHex, - checkResult: (input) => - checkResultImpl(input, api, expectedEvents, didDocument.id, signers), - } - } - - const submit: TransactionHandlers['submit'] = (submitOptions) => - submitImpl(getSubmittable, { ...options, ...submitOptions }) - - return { - submit, - getSubmittable, - } -} - -/** - * Creates an on-chain DID based on an authentication key. - * - * @param options.fromPublicKey The public key that will feature as the DID's initial authentication method and will determine the DID identifier. - * @param options - */ -export function createDid( - options: Omit & { - fromPublicKey: AcceptedPublicKeyEncodings - } -): TransactionHandlers { - function implementsSignerInterface(input: any): input is SignerInterface { - return 'algorithm' in input && 'id' in input && 'sign' in input - } - - const getSubmittable: TransactionHandlers['getSubmittable'] = async ( - submitOptions = {} - ) => { - const { fromPublicKey, submitter, signers, api } = options - const { signSubmittable = true } = submitOptions - const { publicKey, keyType } = convertPublicKey(fromPublicKey) - - if (!signingMethodTypes.includes(keyType)) { - throw new Error('invalid public key') - } - const submitterAccount = ( - 'address' in submitter ? submitter.address : submitter.id - ) as KiltAddress - - const accountSigners = ( - await Promise.all( - signers.map(async (signer) => { - if (implementsSignerInterface(signer)) { - return [signer] - } - const res = await Signers.getSignersForKeypair({ - keypair: signer, - }) - return res - }) - ) - ).flat() - const didCreation = await getStoreTx( - { - authentication: [{ publicKey, type: keyType as 'sr25519' }], - }, - 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() - } - - return { - txHex: signedHex, - checkResult: (input) => - checkResultImpl( - input, - api, - [{ section: 'did', method: 'DidCreated' }], - getFullDid(Crypto.encodeAddress(publicKey, 38)), - signers - ), - } - } - - const submit: TransactionHandlers['submit'] = (submitOptions) => - submitImpl(getSubmittable, { ...options, ...submitOptions }) - - return { getSubmittable, submit } -} diff --git a/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts b/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts index 7b979a2d6..97595b784 100644 --- a/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts +++ b/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts @@ -6,18 +6,19 @@ */ import { - NewDidEncryptionKey, - NewDidVerificationKey, + type NewDidEncryptionKey, + type NewDidVerificationKey, publicKeyToChain, } from '@kiltprotocol/did' -import { VerificationRelationship } from '@kiltprotocol/types' -import { convertPublicKey } from './createDid.js' -import { transact } from './index.js' +import type { VerificationRelationship } from '@kiltprotocol/types' + +import { convertPublicKey } from './common.js' import type { AcceptedPublicKeyEncodings, SharedArguments, TransactionHandlers, -} from './interfaces' +} from './interfaces.js' +import { transact } from './transact.js' /** * Replaces all existing verification methods for the selected `relationship` with `publicKey`. diff --git a/packages/sdk-js/src/DidHelpers/transact.ts b/packages/sdk-js/src/DidHelpers/transact.ts new file mode 100644 index 000000000..055b5c346 --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/transact.ts @@ -0,0 +1,86 @@ +/** + * 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 type { Extrinsic } from '@polkadot/types/interfaces' + +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.call The transaction / call to execute. + * @returns + */ +export function transact( + options: SharedArguments & { + call: Extrinsic | SubmittableExtrinsic + expectedEvents?: Array<{ section: string; method: string }> + } +): TransactionHandlers { + const getSubmittable: TransactionHandlers['getSubmittable'] = async ( + submitOptions: + | { + signSubmittable?: boolean // default: true + didNonce?: number | BigInt + } + | undefined = {} + ) => { + const { didDocument, signers, submitter, call, api, expectedEvents } = + options + const { didNonce, signSubmittable = true } = submitOptions + const didSigners = await signersForDid(didDocument, ...signers) + + const submitterAccount = ( + 'address' in submitter ? submitter.address : submitter.id + ) as KiltAddress + + const authorized: SubmittableExtrinsic = await authorizeTx( + didDocument, + call, + didSigners, + submitterAccount, + typeof didNonce !== 'undefined' + ? { + txCounter: api.createType('u64', didNonce), + } + : {} + ) + + 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() + } + + return { + txHex: signedHex, + checkResult: (input) => + checkResultImpl(input, api, expectedEvents, didDocument.id, signers), + } + } + + const submit: TransactionHandlers['submit'] = (submitOptions) => + submitImpl(getSubmittable, { ...options, ...submitOptions }) + + return { + submit, + getSubmittable, + } +} From 23be55af8e60700173d45a137aa8b34c4129c33a Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 16 Jul 2024 13:52:07 +0200 Subject: [PATCH 14/70] fix: setVerificationMethod --- .../sdk-js/src/DidHelpers/setVerificationMethod.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts b/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts index 97595b784..2b657e3cf 100644 --- a/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts +++ b/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts @@ -38,15 +38,15 @@ export function setVerificationMethod( if (options.relationship === 'keyAgreement') { // TODO: check if types of keys are valid? - const didAuthKey: NewDidEncryptionKey = { + const didEncryptionKey: NewDidEncryptionKey = { publicKey: pk.publicKey, type: pk.keyType as any, } didKeyUpdateTx = options.api.tx.did.addKeyAgreementKey( - publicKeyToChain(didAuthKey) + publicKeyToChain(didEncryptionKey) ) } else { - const didAuthKey: NewDidVerificationKey = { + const didVerificationKey: NewDidVerificationKey = { publicKey: pk.publicKey, type: pk.keyType as any, } @@ -54,19 +54,19 @@ export function setVerificationMethod( switch (options.relationship) { case 'authentication': { didKeyUpdateTx = options.api.tx.did.setAuthenticationKey( - publicKeyToChain(didAuthKey) + publicKeyToChain(didVerificationKey) ) break } case 'capabilityDelegation': { didKeyUpdateTx = options.api.tx.did.setDelegationKey( - publicKeyToChain(didAuthKey) + publicKeyToChain(didVerificationKey) ) break } case 'assertionMethod': { didKeyUpdateTx = options.api.tx.did.assertionMethod( - publicKeyToChain(didAuthKey) + publicKeyToChain(didVerificationKey) ) break } From 0bbe3bfa81766a3f8a1362945d2e11d728025342 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 16 Jul 2024 14:26:00 +0200 Subject: [PATCH 15/70] feat: add service methods --- packages/sdk-js/src/DidHelpers/service.ts | 55 +++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 packages/sdk-js/src/DidHelpers/service.ts diff --git a/packages/sdk-js/src/DidHelpers/service.ts b/packages/sdk-js/src/DidHelpers/service.ts new file mode 100644 index 000000000..50beabab1 --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/service.ts @@ -0,0 +1,55 @@ +/** + * 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 { serviceToChain, urlFragmentToChain } from '@kiltprotocol/did' +import { DidUrl, Service, UriFragment } from '@kiltprotocol/types' +import { SharedArguments, TransactionHandlers } from './interfaces.js' +import { transact } from './transact.js' + +/** + * Adds a service to the DID Document. + * + * @param options.service The service entry to add to the document. + * If the service id is relative (begins with #) it is automatically expanded with the DID taken from didDocument.id. + * @param options + */ +export function addService( + options: SharedArguments & { + service: Service + } +): TransactionHandlers { + const didServiceUpdateTx = options.api.tx.did.addServiceEndpoint( + serviceToChain(options.service) + ) + return transact({ + ...options, + call: didServiceUpdateTx, + expectedEvents: [{ section: 'did', method: 'DidUpdated' }], + }) +} + +/** + * Removes a service from the DID Document. + * + * @param options.id The id of the service to remove from the document. + * If the service id is relative (begins with #) it is automatically expanded with the DID taken from didDocument.id. + * @param options + */ +export function removeService( + options: SharedArguments & { + id: DidUrl | UriFragment + } +): TransactionHandlers { + const didServiceUpdateTx = options.api.tx.did.removeServiceEndpoint( + urlFragmentToChain(options.id) + ) + return transact({ + ...options, + call: didServiceUpdateTx, + expectedEvents: [{ section: 'did', method: 'DidUpdated' }], + }) +} From 101ae79d962140a663385f7fd3529a3682dbebcb Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 16 Jul 2024 15:04:21 +0200 Subject: [PATCH 16/70] feat: implement claimWeb3Name --- packages/sdk-js/src/DidHelpers/index.ts | 1 + .../sdk-js/src/DidHelpers/w3names.spec.ts | 71 +++++++++++++++++++ packages/sdk-js/src/DidHelpers/w3names.ts | 30 ++++++++ 3 files changed, 102 insertions(+) create mode 100644 packages/sdk-js/src/DidHelpers/w3names.spec.ts create mode 100644 packages/sdk-js/src/DidHelpers/w3names.ts diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index de8295262..a779cf596 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -14,6 +14,7 @@ import type { SharedArguments } from './interfaces.js' export { createDid } from './createDid.js' export { setVerificationMethod } from './setVerificationMethod.js' export { transact } from './transact.js' +export { claimWeb3Name } from './w3names.js' /** * Selects and returns a DID signer for a given purpose and algorithm. diff --git a/packages/sdk-js/src/DidHelpers/w3names.spec.ts b/packages/sdk-js/src/DidHelpers/w3names.spec.ts new file mode 100644 index 000000000..cc440ffed --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/w3names.spec.ts @@ -0,0 +1,71 @@ +/** + * 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 type { DidDocument, KiltKeyringPair } from '@kiltprotocol/types' +import { Crypto } from '@kiltprotocol/utils' +import { + ApiMocks, + createLocalDemoFullDidFromKeypair, +} from '../../../../tests/testUtils/index.js' +import { ConfigService } from '../index.js' +import { claimWeb3Name, transact } from './index.js' + +jest.mock('./transact.js') + +const mockedTransact = jest.mocked(transact) +const mockedApi = ApiMocks.createAugmentedApi() + +describe('w3n', () => { + let didDocument: DidDocument + let keypair: KiltKeyringPair + beforeAll(async () => { + ConfigService.set({ api: mockedApi }) + + keypair = Crypto.makeKeypairFromUri('//Alice') + const { id, verificationMethod, authentication } = + await createLocalDemoFullDidFromKeypair(keypair, { + verificationRelationships: new Set(['assertionMethod']), + }) + didDocument = { + id, + authentication, + assertionMethod: authentication, + verificationMethod: verificationMethod?.filter( + (vm) => vm.id === authentication![0] + ), + } + }) + + it('creates a claim w3n tx', async () => { + claimWeb3Name({ + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + name: 'paul', + }) + + expect(mockedTransact).toHaveBeenLastCalledWith( + expect.objectContaining[0]>>({ + call: expect.any(Object), + expectedEvents: expect.arrayContaining([ + { + section: 'web3Names', + method: 'Web3NameClaimed', + }, + ]), + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + }) + ) + expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + method: { args: { name: 'paul' }, method: 'claim', section: 'web3Names' }, + }) + }) +}) diff --git a/packages/sdk-js/src/DidHelpers/w3names.ts b/packages/sdk-js/src/DidHelpers/w3names.ts new file mode 100644 index 000000000..ecaafba3a --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/w3names.ts @@ -0,0 +1,30 @@ +/** + * 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 type { SharedArguments, TransactionHandlers } from './interfaces.js' +import { transact } from './transact.js' + +/** + * Adds a w3n nickname to the DID Document. + * + * @param options Any {@link SharedArguments} and additional parameters. + * @param options.name The name to be claimed. + * Must be still available (not yet claimed by another DID) for this operation to succeed. + * @returns A set of {@link TransactionHandlers}. + */ +export function claimWeb3Name( + options: SharedArguments & { + name: string + } +): TransactionHandlers { + const { api, name } = options + return transact({ + ...options, + call: api.tx.web3Names.claim(name), + expectedEvents: [{ section: 'web3Names', method: 'Web3NameClaimed' }], + }) +} From 44bf0498b8db644bbf4470bff63d54e5b5705ca1 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 16 Jul 2024 15:16:16 +0200 Subject: [PATCH 17/70] feat: implement releaseWeb3Name --- packages/sdk-js/src/DidHelpers/index.ts | 2 +- .../sdk-js/src/DidHelpers/w3names.spec.ts | 30 ++++++++++++++++++- packages/sdk-js/src/DidHelpers/w3names.ts | 15 ++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index a779cf596..d54f51fdd 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -14,7 +14,7 @@ import type { SharedArguments } from './interfaces.js' export { createDid } from './createDid.js' export { setVerificationMethod } from './setVerificationMethod.js' export { transact } from './transact.js' -export { claimWeb3Name } from './w3names.js' +export { claimWeb3Name, releaseWeb3Name } from './w3names.js' /** * Selects and returns a DID signer for a given purpose and algorithm. diff --git a/packages/sdk-js/src/DidHelpers/w3names.spec.ts b/packages/sdk-js/src/DidHelpers/w3names.spec.ts index cc440ffed..e078b5d4d 100644 --- a/packages/sdk-js/src/DidHelpers/w3names.spec.ts +++ b/packages/sdk-js/src/DidHelpers/w3names.spec.ts @@ -12,7 +12,7 @@ import { createLocalDemoFullDidFromKeypair, } from '../../../../tests/testUtils/index.js' import { ConfigService } from '../index.js' -import { claimWeb3Name, transact } from './index.js' +import { claimWeb3Name, releaseWeb3Name, transact } from './index.js' jest.mock('./transact.js') @@ -68,4 +68,32 @@ describe('w3n', () => { method: { args: { name: 'paul' }, method: 'claim', section: 'web3Names' }, }) }) + + it('creates a release w3n tx', async () => { + releaseWeb3Name({ + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + }) + + expect(mockedTransact).toHaveBeenLastCalledWith( + expect.objectContaining[0]>>({ + call: expect.any(Object), + expectedEvents: expect.arrayContaining([ + { + section: 'web3Names', + method: 'Web3NameReleased', + }, + ]), + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + }) + ) + expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + method: { method: 'releaseByOwner', section: 'web3Names' }, + }) + }) }) diff --git a/packages/sdk-js/src/DidHelpers/w3names.ts b/packages/sdk-js/src/DidHelpers/w3names.ts index ecaafba3a..0b58afdbd 100644 --- a/packages/sdk-js/src/DidHelpers/w3names.ts +++ b/packages/sdk-js/src/DidHelpers/w3names.ts @@ -28,3 +28,18 @@ export function claimWeb3Name( expectedEvents: [{ section: 'web3Names', method: 'Web3NameClaimed' }], }) } + +/** + * Removes w3n nickname from the DID Document, allowing it to be claimed by others. + * + * @param options Any {@link SharedArguments} and additional parameters. + * @returns A set of {@link TransactionHandlers}. + */ +export function releaseWeb3Name(options: SharedArguments): TransactionHandlers { + const { api } = options + return transact({ + ...options, + call: api.tx.web3Names.releaseByOwner(), + expectedEvents: [{ section: 'web3Names', method: 'Web3NameReleased' }], + }) +} From a21b675604c59fcc97c042372cc382543cc98579 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 16 Jul 2024 15:44:58 +0200 Subject: [PATCH 18/70] fix: module error matching --- packages/sdk-js/src/DidHelpers/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk-js/src/DidHelpers/common.ts b/packages/sdk-js/src/DidHelpers/common.ts index 21887d043..2c3c39944 100644 --- a/packages/sdk-js/src/DidHelpers/common.ts +++ b/packages/sdk-js/src/DidHelpers/common.ts @@ -35,7 +35,7 @@ import type { function mapError(err: SpRuntimeDispatchError, api: ApiPromise): Error { if (err.isModule) { - const { docs, method, section } = api.findError(err.asModule.index.toU8a()) + const { docs, method, section } = api.registry.findMetaError(err.asModule) return new Error(`${section}.${method}: ${docs}`) } return new Error(`${err.type}: ${err.value.toHuman()}`) From 07e0096d4899c69e59c7b165e18ff7e9ae65ceed Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 16 Jul 2024 15:53:52 +0200 Subject: [PATCH 19/70] test: integration tests for w3n helpers --- tests/integration/didHelpers.spec.ts | 115 +++++++++++++++++++-------- 1 file changed, 84 insertions(+), 31 deletions(-) diff --git a/tests/integration/didHelpers.spec.ts b/tests/integration/didHelpers.spec.ts index 0d92fcc16..3da557372 100644 --- a/tests/integration/didHelpers.spec.ts +++ b/tests/integration/didHelpers.spec.ts @@ -5,57 +5,110 @@ * found in the LICENSE file in the root directory of this source tree. */ -import { disconnect } from '@kiltprotocol/sdk-js' -import * as SDK from '@kiltprotocol/sdk-js' -import { KiltKeyringPair } from '@kiltprotocol/types' -import { Crypto } from '@kiltprotocol/utils' import type { ApiPromise } from '@polkadot/api' -import { - AcceptedPublicKeyEncodings, - SharedArguments, -} from 'sdk-js/src/DidHelpers/interfaces' -import { createEndowedTestAccount, initializeApi } from './utils' -// Create did on chain -describe('createDid', () => { - let paymentAccount: KiltKeyringPair - let api: ApiPromise +import { DidHelpers, disconnect } from '@kiltprotocol/sdk-js' +import type { + DidDocument, + KeyringPair, + KiltKeyringPair, +} from '@kiltprotocol/types' +import { Crypto } from '@kiltprotocol/utils' - beforeAll(async () => { - api = await initializeApi() - }, 30_000) +import { createEndowedTestAccount, initializeApi } from './utils.js' - beforeAll(async () => { - paymentAccount = await createEndowedTestAccount() - }, 30_000) +let api: ApiPromise +beforeAll(async () => { + api = await initializeApi() +}, 30_000) + +let paymentAccount: KiltKeyringPair +beforeAll(async () => { + paymentAccount = await createEndowedTestAccount() +}, 30_000) +// Create did on chain +describe('createDid', () => { it('works', async () => { const kp = Crypto.makeKeypairFromUri( 'build hill second flame trigger simple rigid cabbage phrase evolve final eight', 'sr25519' ) - console.log(kp.address) - const options: Omit & { - fromPublicKey: AcceptedPublicKeyEncodings - } = { + + const result = await DidHelpers.createDid({ api, signers: [kp], submitter: paymentAccount, fromPublicKey: kp, - } - - // const did = await (await createDid(options)).submit() - const result = await SDK.DidHelpers.createDid(options).submit() + }).submit() expect(result.status).toBe('confirmed') expect(result.asConfirmed.didDocument).toMatchObject({ id: `did:kilt:${kp.address}`, }) - - // const did2 = await did.getSubmittable({ signSubmittable: false }) }, 60000) +}) - afterAll(async () => { - disconnect() +describe('w3ns', () => { + let keypair: KeyringPair + let didDocument: DidDocument + beforeAll(async () => { + keypair = Crypto.makeKeypairFromUri('//Blob') + const result = await DidHelpers.createDid({ + api, + signers: [keypair], + submitter: paymentAccount, + fromPublicKey: keypair, + }).submit() + didDocument = result.asConfirmed.didDocument }) + + it('claims w3n', async () => { + const result = await DidHelpers.claimWeb3Name({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + name: 'blob', + }).submit() + expect(result.status).toStrictEqual('confirmed') + didDocument = result.asConfirmed.didDocument + expect(didDocument).toHaveProperty( + 'alsoKnownAs', + expect.arrayContaining(['w3n:blob']) + ) + }, 30_000) + + it('fails when trying to claim a 2nd w3n', async () => { + const result = await DidHelpers.claimWeb3Name({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + name: 'blarb', + }).submit() + + expect(result.status).toStrictEqual('failed') + expect(result.asFailed.error).toMatchInlineSnapshot( + `[Error: web3Names.OwnerAlreadyExists: The specified owner already owns a name.]` + ) + expect(result.asFailed.didDocument).toMatchObject(didDocument) + }, 30_000) + + it('releases a w3n', async () => { + const result = await DidHelpers.releaseWeb3Name({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + }).submit() + + expect(result.status).toStrictEqual('confirmed') + didDocument = result.asConfirmed.didDocument + expect(didDocument).not.toHaveProperty('alsoKnownAs') + }, 30_000) +}) + +afterAll(async () => { + await disconnect() }) From 89b434b3a2a880e29c02f064a57a6335f8743924 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 16 Jul 2024 18:05:22 +0200 Subject: [PATCH 20/70] test: add unit tests for addService & removeService --- .../sdk-js/src/DidHelpers/service.spec.ts | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 packages/sdk-js/src/DidHelpers/service.spec.ts diff --git a/packages/sdk-js/src/DidHelpers/service.spec.ts b/packages/sdk-js/src/DidHelpers/service.spec.ts new file mode 100644 index 000000000..79738ad0d --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/service.spec.ts @@ -0,0 +1,121 @@ +/** + * 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 type { DidDocument, KiltKeyringPair } from '@kiltprotocol/types' +import { Crypto } from '@kiltprotocol/utils' +import { + ApiMocks, + createLocalDemoFullDidFromKeypair, +} from '../../../../tests/testUtils/index.js' +import { ConfigService } from '../index.js' +import { transact } from './index.js' +import { addService, removeService } from './service.js' + +jest.mock('./transact.js') + +const mockedTransact = jest.mocked(transact) +const mockedApi = ApiMocks.createAugmentedApi() + +describe('service management', () => { + let didDocument: DidDocument + let keypair: KiltKeyringPair + beforeAll(async () => { + ConfigService.set({ api: mockedApi }) + + keypair = Crypto.makeKeypairFromUri('//Alice') + const { id, verificationMethod, authentication } = + await createLocalDemoFullDidFromKeypair(keypair, { + verificationRelationships: new Set(['assertionMethod']), + }) + didDocument = { + id, + authentication, + assertionMethod: authentication, + verificationMethod: verificationMethod?.filter( + (vm) => vm.id === authentication![0] + ), + } + }) + + it('creates an add service tx', async () => { + addService({ + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + service: { + id: '#my_service', + type: ['http://schema.org/EmailService'], + serviceEndpoint: ['mailto:info@kilt.io'], + }, + }) + + expect(mockedTransact).toHaveBeenLastCalledWith( + expect.objectContaining[0]>>({ + call: expect.any(Object), + expectedEvents: expect.arrayContaining([ + { + section: 'did', + method: 'DidUpdated', + }, + ]), + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + }) + ) + expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + method: { + args: { + service_endpoint: { + id: 'my_service', + serviceTypes: ['http://schema.org/EmailService'], + urls: ['mailto:info@kilt.io'], + }, + }, + section: 'did', + method: 'addServiceEndpoint', + }, + }) + }) + + it('creates a remove service tx', async () => { + removeService({ + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + id: '#my_service', + }) + + expect(mockedTransact).toHaveBeenLastCalledWith( + expect.objectContaining[0]>>({ + call: expect.any(Object), + expectedEvents: expect.arrayContaining([ + { + section: 'did', + method: 'DidUpdated', + }, + ]), + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + }) + ) + expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + method: { + args: { + service_id: 'my_service', + }, + section: 'did', + method: 'removeServiceEndpoint', + }, + }) + }) +}) From c2070326111d88201c62ecbb82612139d14852bb Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 16 Jul 2024 18:20:00 +0200 Subject: [PATCH 21/70] test: run service unit tests for fragment- and full ids --- .../sdk-js/src/DidHelpers/service.spec.ts | 165 +++++++++--------- 1 file changed, 84 insertions(+), 81 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/service.spec.ts b/packages/sdk-js/src/DidHelpers/service.spec.ts index 79738ad0d..0e5b7f91c 100644 --- a/packages/sdk-js/src/DidHelpers/service.spec.ts +++ b/packages/sdk-js/src/DidHelpers/service.spec.ts @@ -5,7 +5,7 @@ * found in the LICENSE file in the root directory of this source tree. */ -import type { DidDocument, KiltKeyringPair } from '@kiltprotocol/types' +import type { DidDocument, DidUrl, KiltKeyringPair } from '@kiltprotocol/types' import { Crypto } from '@kiltprotocol/utils' import { ApiMocks, @@ -20,102 +20,105 @@ jest.mock('./transact.js') const mockedTransact = jest.mocked(transact) const mockedApi = ApiMocks.createAugmentedApi() -describe('service management', () => { - let didDocument: DidDocument - let keypair: KiltKeyringPair - beforeAll(async () => { - ConfigService.set({ api: mockedApi }) +describe.each(['#my_service', 'did:kilt:4abctest#my_service'])( + 'service management with id %s', + (serviceId) => { + let didDocument: DidDocument + let keypair: KiltKeyringPair + beforeAll(async () => { + ConfigService.set({ api: mockedApi }) - keypair = Crypto.makeKeypairFromUri('//Alice') - const { id, verificationMethod, authentication } = - await createLocalDemoFullDidFromKeypair(keypair, { - verificationRelationships: new Set(['assertionMethod']), - }) - didDocument = { - id, - authentication, - assertionMethod: authentication, - verificationMethod: verificationMethod?.filter( - (vm) => vm.id === authentication![0] - ), - } - }) - - it('creates an add service tx', async () => { - addService({ - didDocument, - api: mockedApi, - submitter: keypair, - signers: [keypair], - service: { - id: '#my_service', - type: ['http://schema.org/EmailService'], - serviceEndpoint: ['mailto:info@kilt.io'], - }, + keypair = Crypto.makeKeypairFromUri('//Alice') + const { id, verificationMethod, authentication } = + await createLocalDemoFullDidFromKeypair(keypair, { + verificationRelationships: new Set(['assertionMethod']), + }) + didDocument = { + id, + authentication, + assertionMethod: authentication, + verificationMethod: verificationMethod?.filter( + (vm) => vm.id === authentication![0] + ), + } }) - expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), - expectedEvents: expect.arrayContaining([ - { - section: 'did', - method: 'DidUpdated', - }, - ]), + it('creates an add service tx', async () => { + addService({ didDocument, api: mockedApi, submitter: keypair, signers: [keypair], + service: { + id: serviceId as DidUrl, + type: ['http://schema.org/EmailService'], + serviceEndpoint: ['mailto:info@kilt.io'], + }, }) - ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ - method: { - args: { - service_endpoint: { - id: 'my_service', - serviceTypes: ['http://schema.org/EmailService'], - urls: ['mailto:info@kilt.io'], + + expect(mockedTransact).toHaveBeenLastCalledWith( + expect.objectContaining[0]>>({ + call: expect.any(Object), + expectedEvents: expect.arrayContaining([ + { + section: 'did', + method: 'DidUpdated', + }, + ]), + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + }) + ) + expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + method: { + args: { + service_endpoint: { + id: 'my_service', + serviceTypes: ['http://schema.org/EmailService'], + urls: ['mailto:info@kilt.io'], + }, }, + section: 'did', + method: 'addServiceEndpoint', }, - section: 'did', - method: 'addServiceEndpoint', - }, - }) - }) - - it('creates a remove service tx', async () => { - removeService({ - didDocument, - api: mockedApi, - submitter: keypair, - signers: [keypair], - id: '#my_service', + }) }) - expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), - expectedEvents: expect.arrayContaining([ - { - section: 'did', - method: 'DidUpdated', - }, - ]), + it('creates a remove service tx', async () => { + removeService({ didDocument, api: mockedApi, submitter: keypair, signers: [keypair], + id: serviceId as DidUrl, }) - ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ - method: { - args: { - service_id: 'my_service', + + expect(mockedTransact).toHaveBeenLastCalledWith( + expect.objectContaining[0]>>({ + call: expect.any(Object), + expectedEvents: expect.arrayContaining([ + { + section: 'did', + method: 'DidUpdated', + }, + ]), + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + }) + ) + expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + method: { + args: { + service_id: 'my_service', + }, + section: 'did', + method: 'removeServiceEndpoint', }, - section: 'did', - method: 'removeServiceEndpoint', - }, + }) }) - }) -}) + } +) From 2a3fa57dc73b63ed8304aaea13a773d06def0020 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 16 Jul 2024 18:20:38 +0200 Subject: [PATCH 22/70] fix: export service did helpers --- packages/sdk-js/src/DidHelpers/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index d54f51fdd..5d978add1 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -12,6 +12,7 @@ import { Signers } from '@kiltprotocol/utils' import type { SharedArguments } from './interfaces.js' export { createDid } from './createDid.js' +export { addService, removeService } from './service.js' export { setVerificationMethod } from './setVerificationMethod.js' export { transact } from './transact.js' export { claimWeb3Name, releaseWeb3Name } from './w3names.js' From f098ec1b200993813c432044535206c82ae71b99 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 16 Jul 2024 18:22:14 +0200 Subject: [PATCH 23/70] test: service did helpers integration tests --- tests/integration/didHelpers.spec.ts | 57 ++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/integration/didHelpers.spec.ts b/tests/integration/didHelpers.spec.ts index 3da557372..b5c2a5436 100644 --- a/tests/integration/didHelpers.spec.ts +++ b/tests/integration/didHelpers.spec.ts @@ -12,6 +12,7 @@ import type { DidDocument, KeyringPair, KiltKeyringPair, + Service, } from '@kiltprotocol/types' import { Crypto } from '@kiltprotocol/utils' @@ -109,6 +110,62 @@ describe('w3ns', () => { }, 30_000) }) +describe('services', () => { + let keypair: KeyringPair + let didDocument: DidDocument + beforeAll(async () => { + keypair = Crypto.makeKeypairFromUri('//Services') + const result = await DidHelpers.createDid({ + api, + signers: [keypair], + submitter: paymentAccount, + fromPublicKey: keypair, + }).submit() + didDocument = result.asConfirmed.didDocument + }) + + it('adds a service', async () => { + const result = await DidHelpers.addService({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + service: { + id: '#my_service', + type: ['http://schema.org/EmailService'], + serviceEndpoint: ['mailto:info@kilt.io'], + }, + }).submit() + expect(result.status).toStrictEqual('confirmed') + didDocument = result.asConfirmed.didDocument + expect(didDocument).toHaveProperty( + 'service', + expect.arrayContaining([ + { + id: `${didDocument.id}#my_service`, + type: ['http://schema.org/EmailService'], + serviceEndpoint: ['mailto:info@kilt.io'], + }, + ]) + ) + expect(didDocument.service).toHaveLength(1) + }, 30_000) + + it('removes a service', async () => { + const result = await DidHelpers.removeService({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + id: '#my_service', + }).submit() + + expect(result.status).toStrictEqual('confirmed') + didDocument = result.asConfirmed.didDocument + expect(didDocument).not.toHaveProperty('service') + }, 30_000) +}) + afterAll(async () => { await disconnect() }) From ec0978b91b378beb1deb28282efd510a6f263629 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner <39338561+rflechtner@users.noreply.github.com> Date: Wed, 17 Jul 2024 11:12:22 +0200 Subject: [PATCH 24/70] fix: setVerificationMethod extrinsics and logic (#878) * fix: make sure existing keyAgreement VMs are replaced * fix: use correct extrinsic for assertionMethod --- .../src/DidHelpers/setVerificationMethod.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts b/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts index 2b657e3cf..bd5d05706 100644 --- a/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts +++ b/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts @@ -9,8 +9,12 @@ import { type NewDidEncryptionKey, type NewDidVerificationKey, publicKeyToChain, + urlFragmentToChain, } from '@kiltprotocol/did' -import type { VerificationRelationship } from '@kiltprotocol/types' +import type { + SubmittableExtrinsic, + VerificationRelationship, +} from '@kiltprotocol/types' import { convertPublicKey } from './common.js' import type { @@ -42,9 +46,14 @@ export function setVerificationMethod( publicKey: pk.publicKey, type: pk.keyType as any, } - didKeyUpdateTx = options.api.tx.did.addKeyAgreementKey( - publicKeyToChain(didEncryptionKey) + 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, @@ -65,7 +74,7 @@ export function setVerificationMethod( break } case 'assertionMethod': { - didKeyUpdateTx = options.api.tx.did.assertionMethod( + didKeyUpdateTx = options.api.tx.did.setAttestationKey( publicKeyToChain(didVerificationKey) ) break From 7a55e999dff63897dd056a1fcb933cd7a6e0f391 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 16 Jul 2024 16:53:11 +0200 Subject: [PATCH 25/70] feat: removeVM --- packages/sdk-js/src/DidHelpers/service.ts | 8 +- .../src/DidHelpers/setVerificationMethod.ts | 85 ++++++++++++++++++- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/service.ts b/packages/sdk-js/src/DidHelpers/service.ts index 50beabab1..7a47018ba 100644 --- a/packages/sdk-js/src/DidHelpers/service.ts +++ b/packages/sdk-js/src/DidHelpers/service.ts @@ -6,16 +6,17 @@ */ import { serviceToChain, urlFragmentToChain } from '@kiltprotocol/did' -import { DidUrl, Service, UriFragment } from '@kiltprotocol/types' +import type { DidUrl, Service, UriFragment } from '@kiltprotocol/types' import { SharedArguments, TransactionHandlers } from './interfaces.js' import { transact } from './transact.js' /** * Adds a service to the DID Document. * + * @param options Any {@link SharedArguments} and additional parameters. * @param options.service The service entry to add to the document. * If the service id is relative (begins with #) it is automatically expanded with the DID taken from didDocument.id. - * @param options + * @returns A set of {@link TransactionHandlers}. */ export function addService( options: SharedArguments & { @@ -35,9 +36,10 @@ export function addService( /** * Removes a service from the DID Document. * + * @param options Any {@link SharedArguments} and additional parameters. * @param options.id The id of the service to remove from the document. * If the service id is relative (begins with #) it is automatically expanded with the DID taken from didDocument.id. - * @param options + * @returns A set of {@link TransactionHandlers}. */ export function removeService( options: SharedArguments & { diff --git a/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts b/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts index bd5d05706..6c9453103 100644 --- a/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts +++ b/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts @@ -12,6 +12,7 @@ import { urlFragmentToChain, } from '@kiltprotocol/did' import type { + DidUrl, SubmittableExtrinsic, VerificationRelationship, } from '@kiltprotocol/types' @@ -27,9 +28,11 @@ import { transact } from './transact.js' /** * Replaces all existing verification methods for the selected `relationship` with `publicKey`. * + * @param options Any {@link SharedArguments} and additional parameters. * @param options.publicKey The public key to be used for this verification method. * @param options.relationship The relationship for which this verification method shall be useable. - * @param options + * + * @returns A set of {@link TransactionHandlers}. */ export function setVerificationMethod( options: SharedArguments & { @@ -41,7 +44,6 @@ export function setVerificationMethod( let didKeyUpdateTx if (options.relationship === 'keyAgreement') { - // TODO: check if types of keys are valid? const didEncryptionKey: NewDidEncryptionKey = { publicKey: pk.publicKey, type: pk.keyType as any, @@ -91,3 +93,82 @@ export function setVerificationMethod( expectedEvents: [{ section: 'did', method: 'DidUpdated' }], }) } + +/** + * Removes the verification method for the selected `verificationMethodId` and `relationship`. + * + * Note: authentication verification method can not be removed. + * + * @param options Any {@link SharedArguments} and additional parameters. + * @param options.relationship The relationship for which this verification method shall be useable. + * @param options.verificationMethodId The id of the verification method that should be removed. + * + * @returns A set of {@link TransactionHandlers}. + */ +export function removeVerificationMethod( + options: SharedArguments & { + verificationMethodId: DidUrl + relationship: Omit + } +): TransactionHandlers { + let didKeyUpdateTx + switch (options.relationship) { + case 'authentication': { + throw new Error('authentication verification methods can not be removed') + } + case 'capabilityDelegation': { + if ( + options.didDocument.capabilityDelegation && + options.didDocument.capabilityDelegation.includes( + options.verificationMethodId + ) + ) { + didKeyUpdateTx = options.api.tx.did.removeDelegationKey() + } else { + throw new Error( + 'the specified capabilityDelegation method does not exist in the DID Document' + ) + } + break + } + case 'keyAgreement': { + if ( + options.didDocument.keyAgreement && + options.didDocument.keyAgreement.includes(options.verificationMethodId) + ) { + didKeyUpdateTx = options.api.tx.did.removeKeyAgreementKey( + urlFragmentToChain(options.verificationMethodId) + ) + } else { + throw new Error( + 'the specified keyAgreement key does not exist in the DID Document' + ) + } + break + } + case 'assertionMethod': { + if ( + options.didDocument.assertionMethod && + options.didDocument.assertionMethod.includes( + options.verificationMethodId + ) + ) { + didKeyUpdateTx = options.api.tx.did.removeAttestationKey() + } else { + throw new Error( + 'the specified assertionMethod does not exist in the DID Document' + ) + } + break + } + default: { + throw new Error('the specified method relationship is not supported') + } + } + + return transact({ + ...options, + call: didKeyUpdateTx, + expectedEvents: [{ section: 'did', method: 'DidUpdated' }], + }) +} From ed974a3115e043356d5005be135d69edc5221d9f Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 16 Jul 2024 17:22:30 +0200 Subject: [PATCH 26/70] fix: documentation --- .../sdk-js/src/DidHelpers/setVerificationMethod.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts b/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts index 6c9453103..21ec5c3d0 100644 --- a/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts +++ b/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts @@ -100,7 +100,7 @@ export function setVerificationMethod( * Note: authentication verification method can not be removed. * * @param options Any {@link SharedArguments} and additional parameters. - * @param options.relationship The relationship for which this verification method shall be useable. + * @param options.relationship The relationship for which this verification method shall be removed. * @param options.verificationMethodId The id of the verification method that should be removed. * * @returns A set of {@link TransactionHandlers}. @@ -118,8 +118,7 @@ export function removeVerificationMethod( } case 'capabilityDelegation': { if ( - options.didDocument.capabilityDelegation && - options.didDocument.capabilityDelegation.includes( + options.didDocument.capabilityDelegation?.includes( options.verificationMethodId ) ) { @@ -133,8 +132,7 @@ export function removeVerificationMethod( } case 'keyAgreement': { if ( - options.didDocument.keyAgreement && - options.didDocument.keyAgreement.includes(options.verificationMethodId) + options.didDocument.keyAgreement?.includes(options.verificationMethodId) ) { didKeyUpdateTx = options.api.tx.did.removeKeyAgreementKey( urlFragmentToChain(options.verificationMethodId) @@ -148,8 +146,7 @@ export function removeVerificationMethod( } case 'assertionMethod': { if ( - options.didDocument.assertionMethod && - options.didDocument.assertionMethod.includes( + options.didDocument.assertionMethod?.includes( options.verificationMethodId ) ) { From bc59831aea269e2ce743d537ff7e5329e716f680 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 16 Jul 2024 21:41:48 +0200 Subject: [PATCH 27/70] fix: export removeVerificationMethod --- packages/sdk-js/src/DidHelpers/index.ts | 5 ++++- packages/sdk-js/src/DidHelpers/service.ts | 2 +- .../{setVerificationMethod.ts => verificationMethod.ts} | 0 3 files changed, 5 insertions(+), 2 deletions(-) rename packages/sdk-js/src/DidHelpers/{setVerificationMethod.ts => verificationMethod.ts} (100%) diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index 5d978add1..d10bf9c2a 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -13,8 +13,11 @@ import type { SharedArguments } from './interfaces.js' export { createDid } from './createDid.js' export { addService, removeService } from './service.js' -export { setVerificationMethod } from './setVerificationMethod.js' export { transact } from './transact.js' +export { + removeVerificationMethod, + setVerificationMethod, +} from './verificationMethod.js' export { claimWeb3Name, releaseWeb3Name } from './w3names.js' /** diff --git a/packages/sdk-js/src/DidHelpers/service.ts b/packages/sdk-js/src/DidHelpers/service.ts index 7a47018ba..b2aee90dd 100644 --- a/packages/sdk-js/src/DidHelpers/service.ts +++ b/packages/sdk-js/src/DidHelpers/service.ts @@ -6,7 +6,7 @@ */ import { serviceToChain, urlFragmentToChain } from '@kiltprotocol/did' -import type { DidUrl, Service, UriFragment } from '@kiltprotocol/types' +import type { DidUrl, Service, UriFragment } from '@kiltprotocol/types' import { SharedArguments, TransactionHandlers } from './interfaces.js' import { transact } from './transact.js' diff --git a/packages/sdk-js/src/DidHelpers/setVerificationMethod.ts b/packages/sdk-js/src/DidHelpers/verificationMethod.ts similarity index 100% rename from packages/sdk-js/src/DidHelpers/setVerificationMethod.ts rename to packages/sdk-js/src/DidHelpers/verificationMethod.ts From 409e2d473de6c7be678a41009131b672b9dd7884 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 16 Jul 2024 21:42:13 +0200 Subject: [PATCH 28/70] test: add verificationMethods unit tests --- .../src/DidHelpers/verificationMethod.spec.ts | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 packages/sdk-js/src/DidHelpers/verificationMethod.spec.ts diff --git a/packages/sdk-js/src/DidHelpers/verificationMethod.spec.ts b/packages/sdk-js/src/DidHelpers/verificationMethod.spec.ts new file mode 100644 index 000000000..5c5e2c130 --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/verificationMethod.spec.ts @@ -0,0 +1,241 @@ +/** + * 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 type { DidDocument, KiltKeyringPair } from '@kiltprotocol/types' +import { Crypto } from '@kiltprotocol/utils' +import { + ApiMocks, + createLocalDemoFullDidFromKeypair, +} from '../../../../tests/testUtils/index.js' +import { ConfigService } from '../index.js' +import { + removeVerificationMethod, + setVerificationMethod, +} from './verificationMethod.js' +import { transact } from './transact.js' + +jest.mock('./transact.js') + +const mockedTransact = jest.mocked(transact) +const mockedApi = ApiMocks.createAugmentedApi() + +let didDocument: DidDocument +let keypair: KiltKeyringPair +beforeAll(async () => { + ConfigService.set({ api: mockedApi }) + + keypair = Crypto.makeKeypairFromUri('//Alice') + const { id, verificationMethod, authentication } = + await createLocalDemoFullDidFromKeypair(keypair, { + verificationRelationships: new Set(['assertionMethod']), + }) + didDocument = { + id, + authentication, + assertionMethod: authentication, + verificationMethod: verificationMethod?.filter( + (vm) => vm.id === authentication![0] + ), + } +}) + +describe('signing keys', () => { + it('creates a set VM tx', async () => { + setVerificationMethod({ + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + publicKey: keypair, + relationship: 'assertionMethod', + }) + + expect(mockedTransact).toHaveBeenLastCalledWith( + expect.objectContaining[0]>>({ + call: expect.any(Object), + expectedEvents: expect.arrayContaining([ + { + section: 'did', + method: 'DidUpdated', + }, + ]), + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + }) + ) + expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + method: { + section: 'did', + method: 'setAttestationKey', + args: { new_key: { Ed25519: Crypto.u8aToHex(keypair.publicKey) } }, + }, + }) + }) + + it('creates a remove VM tx', async () => { + didDocument.assertionMethod = didDocument.authentication + removeVerificationMethod({ + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + verificationMethodId: didDocument.assertionMethod![0], + relationship: 'assertionMethod', + }) + + expect(mockedTransact).toHaveBeenLastCalledWith( + expect.objectContaining[0]>>({ + call: expect.any(Object), + expectedEvents: expect.arrayContaining([ + { + section: 'did', + method: 'DidUpdated', + }, + ]), + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + }) + ) + expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + method: { + section: 'did', + method: 'removeAttestationKey', + }, + }) + }) +}) + +describe('key agreement keys', () => { + it('creates a set VM tx for the first key agreement', async () => { + setVerificationMethod({ + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + publicKey: { publicKey: keypair.publicKey, type: 'x25519' } as any, + relationship: 'keyAgreement', + }) + + expect(mockedTransact).toHaveBeenLastCalledWith( + expect.objectContaining[0]>>({ + call: expect.any(Object), + expectedEvents: expect.arrayContaining([ + { + section: 'did', + method: 'DidUpdated', + }, + ]), + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + }) + ) + expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + method: { + section: 'utility', + method: 'batchAll', + args: { + calls: [ + { + section: 'did', + method: 'addKeyAgreementKey', + args: { new_key: { X25519: Crypto.u8aToHex(keypair.publicKey) } }, + }, + ], + }, + }, + }) + }) + + it('creates a set VM tx for the second key agreement', async () => { + didDocument.keyAgreement = [ + `${didDocument.id}#${Crypto.hashStr('keyAgreement1')}`, + ] + setVerificationMethod({ + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + publicKey: { publicKey: keypair.publicKey, type: 'x25519' } as any, + relationship: 'keyAgreement', + }) + + expect(mockedTransact).toHaveBeenLastCalledWith( + expect.objectContaining[0]>>({ + call: expect.any(Object), + expectedEvents: expect.arrayContaining([ + { + section: 'did', + method: 'DidUpdated', + }, + ]), + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + }) + ) + expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + method: { + section: 'utility', + method: 'batchAll', + args: { + calls: [ + { + section: 'did', + method: 'removeKeyAgreementKey', + args: { key_id: expect.stringContaining('0x') }, + }, + { + section: 'did', + method: 'addKeyAgreementKey', + args: { new_key: { X25519: Crypto.u8aToHex(keypair.publicKey) } }, + }, + ], + }, + }, + }) + }) + + it('creates a remove VM tx', async () => { + removeVerificationMethod({ + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + verificationMethodId: didDocument.keyAgreement![0], + relationship: 'keyAgreement', + }) + + expect(mockedTransact).toHaveBeenLastCalledWith( + expect.objectContaining[0]>>({ + call: expect.any(Object), + expectedEvents: expect.arrayContaining([ + { + section: 'did', + method: 'DidUpdated', + }, + ]), + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + }) + ) + expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + method: { + section: 'did', + method: 'removeKeyAgreementKey', + }, + }) + }) +}) From c07e3eabc72b13ee7476d8ec012a69aad79eefde Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 17 Jul 2024 10:54:23 +0200 Subject: [PATCH 29/70] fix: add x25519 to allowed public key 'type' values --- packages/sdk-js/src/DidHelpers/interfaces.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/sdk-js/src/DidHelpers/interfaces.ts b/packages/sdk-js/src/DidHelpers/interfaces.ts index 42d7cbcb9..43cf3fb78 100644 --- a/packages/sdk-js/src/DidHelpers/interfaces.ts +++ b/packages/sdk-js/src/DidHelpers/interfaces.ts @@ -114,7 +114,12 @@ export type SharedArguments = { submitter: KeyringPair | Blockchain.TransactionSigner } +type PublicKeyAndType = { + publicKey: Uint8Array + type: KeyringPair['type'] | 'x25519' +} + export type AcceptedPublicKeyEncodings = | KeyMultibaseEncoded | { publicKeyMultibase: KeyMultibaseEncoded } - | Pick // interface allows KeyringPair too + | PublicKeyAndType // interface allows KeyringPair too From 4b9e3defe8841df908f7089333873b70d0a02400 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 17 Jul 2024 10:55:34 +0200 Subject: [PATCH 30/70] test: add VM management integration tests --- tests/integration/didHelpers.spec.ts | 134 +++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/tests/integration/didHelpers.spec.ts b/tests/integration/didHelpers.spec.ts index b5c2a5436..8bf87f091 100644 --- a/tests/integration/didHelpers.spec.ts +++ b/tests/integration/didHelpers.spec.ts @@ -166,6 +166,140 @@ describe('services', () => { }, 30_000) }) +describe('verification methods', () => { + let keypair: KeyringPair + let didDocument: DidDocument + beforeAll(async () => { + keypair = Crypto.makeKeypairFromUri('//Vms') + const result = await DidHelpers.createDid({ + api, + signers: [keypair], + submitter: paymentAccount, + fromPublicKey: keypair, + }).submit() + didDocument = result.asConfirmed.didDocument + }) + + it('sets an assertion method', async () => { + expect(didDocument).not.toHaveProperty('assertionMethod') + const result = await DidHelpers.setVerificationMethod({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + publicKey: keypair, + relationship: 'assertionMethod', + }).submit() + expect(result.status).toStrictEqual('confirmed') + didDocument = result.asConfirmed.didDocument + expect(didDocument).toHaveProperty( + 'assertionMethod', + didDocument.authentication + ) + + const result2 = await DidHelpers.setVerificationMethod({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + publicKey: { publicKey: new Uint8Array(32).fill(1), type: 'ed25519' }, + relationship: 'assertionMethod', + }).submit() + + expect(result2.status).toStrictEqual('confirmed') + didDocument = result2.asConfirmed.didDocument + expect(didDocument.assertionMethod).toHaveLength(1) + expect(didDocument.assertionMethod![0]).not.toEqual( + didDocument.authentication![0] + ) + }, 60_000) + + it('sets a key agreement method', async () => { + expect(didDocument).not.toHaveProperty('keyAgreement') + const result = await DidHelpers.setVerificationMethod({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + publicKey: { publicKey: new Uint8Array(32).fill(0), type: 'x25519' }, + relationship: 'keyAgreement', + }).submit() + expect(result.status).toStrictEqual('confirmed') + didDocument = result.asConfirmed.didDocument + expect(didDocument).toHaveProperty('keyAgreement', expect.any(Array)) + expect(didDocument.keyAgreement).toHaveLength(1) + + const [oldKey] = didDocument.keyAgreement! + + const result2 = await DidHelpers.setVerificationMethod({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + publicKey: { publicKey: new Uint8Array(32).fill(1), type: 'x25519' }, + relationship: 'keyAgreement', + }).submit() + + expect(result2.status).toStrictEqual('confirmed') + didDocument = result2.asConfirmed.didDocument + expect(didDocument.keyAgreement).toHaveLength(1) + expect(didDocument.keyAgreement![0]).not.toEqual(oldKey) + }, 60_000) + + it('removes an assertion method', async () => { + expect(didDocument.assertionMethod).toHaveLength(1) + + const result = await DidHelpers.removeVerificationMethod({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + verificationMethodId: didDocument.assertionMethod![0], + relationship: 'assertionMethod', + }).submit() + + expect(result.status).toStrictEqual('confirmed') + didDocument = result.asConfirmed.didDocument + expect(didDocument).not.toHaveProperty('assertionMethod') + }, 30_000) + + it('removes a key agreement method', async () => { + expect(didDocument.keyAgreement).toHaveLength(1) + + const result = await DidHelpers.removeVerificationMethod({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + verificationMethodId: didDocument.keyAgreement![0], + relationship: 'keyAgreement', + }).submit() + + expect(result.status).toStrictEqual('confirmed') + didDocument = result.asConfirmed.didDocument + expect(didDocument).not.toHaveProperty('keyAgreement') + }, 30_000) + + it('fails to remove authentication method', async () => { + await expect( + Promise.resolve() + .then(() => + DidHelpers.removeVerificationMethod({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + verificationMethodId: didDocument.authentication![0], + relationship: 'authentication', + }).submit() + ) + .then(({ asConfirmed }) => asConfirmed) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"authentication verification methods can not be removed"` + ) + }, 30_000) +}) + afterAll(async () => { await disconnect() }) From 42ed9a6a2d2e72a9bc6aefd3709f51401763d311 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner <39338561+rflechtner@users.noreply.github.com> Date: Wed, 17 Jul 2024 12:09:29 +0200 Subject: [PATCH 31/70] feat: implement deactivateDid (#877) * feat: implement deactivateDid * test: unit tests for deactivateDid * test: add integration tests for deactivateDid --- .../src/DidHelpers/deactivateDid.spec.ts | 71 +++++++++++++++++++ .../sdk-js/src/DidHelpers/deactivateDid.ts | 26 +++++++ packages/sdk-js/src/DidHelpers/index.ts | 1 + tests/integration/didHelpers.spec.ts | 35 +++++++-- 4 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 packages/sdk-js/src/DidHelpers/deactivateDid.spec.ts create mode 100644 packages/sdk-js/src/DidHelpers/deactivateDid.ts diff --git a/packages/sdk-js/src/DidHelpers/deactivateDid.spec.ts b/packages/sdk-js/src/DidHelpers/deactivateDid.spec.ts new file mode 100644 index 000000000..96a7e9a96 --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/deactivateDid.spec.ts @@ -0,0 +1,71 @@ +/** + * 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 type { DidDocument, KiltKeyringPair } from '@kiltprotocol/types' +import { Crypto } from '@kiltprotocol/utils' +import { + ApiMocks, + createLocalDemoFullDidFromKeypair, +} from '../../../../tests/testUtils/index.js' +import { ConfigService } from '../index.js' +import { deactivateDid } from './deactivateDid.js' +import { transact } from './transact.js' + +jest.mock('./transact.js') + +const mockedTransact = jest.mocked(transact) +const mockedApi = ApiMocks.createAugmentedApi() + +describe('deactivate', () => { + let didDocument: DidDocument + let keypair: KiltKeyringPair + beforeAll(async () => { + ConfigService.set({ api: mockedApi }) + + keypair = Crypto.makeKeypairFromUri('//Alice') + const { id, verificationMethod, authentication } = + await createLocalDemoFullDidFromKeypair(keypair, { + verificationRelationships: new Set(['assertionMethod']), + }) + didDocument = { + id, + authentication, + assertionMethod: authentication, + verificationMethod: verificationMethod?.filter( + (vm) => vm.id === authentication![0] + ), + } + }) + + it('creates a deactivate did tx', async () => { + deactivateDid({ + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + }) + + expect(mockedTransact).toHaveBeenLastCalledWith( + expect.objectContaining[0]>>({ + call: expect.any(Object), + expectedEvents: expect.arrayContaining([ + { + section: 'did', + method: 'DidDeleted', + }, + ]), + didDocument, + api: mockedApi, + submitter: keypair, + signers: [keypair], + }) + ) + expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + method: { method: 'delete', section: 'did' }, + }) + }) +}) diff --git a/packages/sdk-js/src/DidHelpers/deactivateDid.ts b/packages/sdk-js/src/DidHelpers/deactivateDid.ts new file mode 100644 index 000000000..ac6318d2b --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/deactivateDid.ts @@ -0,0 +1,26 @@ +/** + * 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 type { SharedArguments, TransactionHandlers } from './interfaces.js' +import { transact } from './transact.js' + +/** + * _Permanently_ deactivates the DID, removing all verification methods and services from its document. + * This action cannot be undone – once a DID has been deactivated, all operations on it (including attempts at re-creation) are permanently disabled. + * + * @param options Any {@link SharedArguments} and additional parameters. + * @param options.didDocument The {@link DidDocument} of the DID to be deactivated. + * @returns A set of {@link TransactionHandlers}. + */ +export function deactivateDid(options: SharedArguments): TransactionHandlers { + const { api, didDocument } = options + return transact({ + ...options, + call: api.tx.did.delete(didDocument.service?.length ?? 0), + expectedEvents: [{ section: 'did', method: 'DidDeleted' }], + }) +} diff --git a/packages/sdk-js/src/DidHelpers/index.ts b/packages/sdk-js/src/DidHelpers/index.ts index d10bf9c2a..488ad0941 100644 --- a/packages/sdk-js/src/DidHelpers/index.ts +++ b/packages/sdk-js/src/DidHelpers/index.ts @@ -12,6 +12,7 @@ import { Signers } from '@kiltprotocol/utils' import type { SharedArguments } from './interfaces.js' export { createDid } from './createDid.js' +export { deactivateDid } from './deactivateDid.js' export { addService, removeService } from './service.js' export { transact } from './transact.js' export { diff --git a/tests/integration/didHelpers.spec.ts b/tests/integration/didHelpers.spec.ts index 8bf87f091..776cfcae3 100644 --- a/tests/integration/didHelpers.spec.ts +++ b/tests/integration/didHelpers.spec.ts @@ -29,13 +29,17 @@ beforeAll(async () => { }, 30_000) // Create did on chain -describe('createDid', () => { - it('works', async () => { - const kp = Crypto.makeKeypairFromUri( +describe('create and deactivate DID', () => { + let kp: KeyringPair + let didDocument: DidDocument + beforeAll(() => { + kp = Crypto.makeKeypairFromUri( 'build hill second flame trigger simple rigid cabbage phrase evolve final eight', 'sr25519' ) + }) + it('creates a DID', async () => { const result = await DidHelpers.createDid({ api, signers: [kp], @@ -44,10 +48,31 @@ describe('createDid', () => { }).submit() expect(result.status).toBe('confirmed') - expect(result.asConfirmed.didDocument).toMatchObject({ + didDocument = result.asConfirmed.didDocument + expect(didDocument).toMatchObject({ id: `did:kilt:${kp.address}`, + verificationMethod: expect.any(Array), + authentication: expect.any(Array), }) - }, 60000) + expect(didDocument.verificationMethod).toHaveProperty('length', 1) + expect(didDocument.authentication).toHaveProperty('length', 1) + }, 30_000) + + it('deactivates the DID', async () => { + const result = await DidHelpers.deactivateDid({ + api, + signers: [kp], + submitter: paymentAccount, + didDocument, + }).submit() + + expect(result.status).toBe('confirmed') + const updatedDoc = result.asConfirmed.didDocument + expect(updatedDoc.id).toStrictEqual(didDocument.id) + expect(updatedDoc).not.toMatchObject(didDocument) + expect(updatedDoc).not.toHaveProperty('authentication') + expect(updatedDoc).not.toHaveProperty('verificationMethod') + }, 30_000) }) describe('w3ns', () => { From 82b0c3ae01e0ebe23bccce7d2f86ed8ac41f568d Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 17 Jul 2024 13:13:00 +0200 Subject: [PATCH 32/70] refactor: reduce getSubmittable code duplication --- .../src/blockchain/Blockchain.ts | 24 +++++++++++++++++++ packages/sdk-js/src/DidHelpers/createDid.ts | 19 +++++---------- packages/sdk-js/src/DidHelpers/transact.ts | 17 ++++--------- 3 files changed, 34 insertions(+), 26 deletions(-) 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/src/DidHelpers/createDid.ts b/packages/sdk-js/src/DidHelpers/createDid.ts index 5a7d6b826..40f6a5a51 100644 --- a/packages/sdk-js/src/DidHelpers/createDid.ts +++ b/packages/sdk-js/src/DidHelpers/createDid.ts @@ -5,6 +5,7 @@ * found in the LICENSE file in the root directory of this source tree. */ +import { Blockchain } from '@kiltprotocol/chain-helpers' import { getFullDid, getStoreTx, signingMethodTypes } from '@kiltprotocol/did' import type { KiltAddress, SignerInterface } from '@kiltprotocol/types' import { Crypto, Signers } from '@kiltprotocol/utils' @@ -39,7 +40,7 @@ export function createDid( const { publicKey, keyType } = convertPublicKey(fromPublicKey) if (!signingMethodTypes.includes(keyType)) { - throw new Error('invalid public key') + throw new Error(`unknown key type ${keyType}`) } const submitterAccount = ( 'address' in submitter ? submitter.address : submitter.id @@ -58,7 +59,8 @@ export function createDid( }) ) ).flat() - const didCreation = await getStoreTx( + + let didCreation = await getStoreTx( { authentication: [{ publicKey, type: keyType as 'sr25519' }], }, @@ -66,21 +68,12 @@ export function createDid( 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/transact.ts b/packages/sdk-js/src/DidHelpers/transact.ts index 055b5c346..ff1a99949 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' @@ -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), } From 4c0ec241bd6c955e88e9aa4b59d7e634eb8ff4da Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 17 Jul 2024 13:19:23 +0200 Subject: [PATCH 33/70] docs: fix incomplete docstrings --- packages/sdk-js/src/DidHelpers/createDid.ts | 3 ++- packages/sdk-js/src/DidHelpers/transact.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/createDid.ts b/packages/sdk-js/src/DidHelpers/createDid.ts index 40f6a5a51..d87520bab 100644 --- a/packages/sdk-js/src/DidHelpers/createDid.ts +++ b/packages/sdk-js/src/DidHelpers/createDid.ts @@ -24,8 +24,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 & { diff --git a/packages/sdk-js/src/DidHelpers/transact.ts b/packages/sdk-js/src/DidHelpers/transact.ts index ff1a99949..40eb7b767 100644 --- a/packages/sdk-js/src/DidHelpers/transact.ts +++ b/packages/sdk-js/src/DidHelpers/transact.ts @@ -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 & { From c6e8ef8f507f3591f88ae520da872e86da17bec0 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 17 Jul 2024 13:19:46 +0200 Subject: [PATCH 34/70] chore: add missing deps --- packages/sdk-js/package.json | 4 +++- yarn.lock | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) 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/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" From e0b95facd15191a97e0bc43335ae08c5f940e308 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 17 Jul 2024 13:41:22 +0200 Subject: [PATCH 35/70] refactor: better error messages on asXxx --- packages/sdk-js/src/DidHelpers/common.ts | 36 +++++++++++++++--------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/common.ts b/packages/sdk-js/src/DidHelpers/common.ts index 2c3c39944..dc5d7bb65 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), } From 8856df3978eb61e4a710f69462522d9241f11f9a Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 17 Jul 2024 13:46:05 +0200 Subject: [PATCH 36/70] refactor: move selectSigner to separate file --- packages/sdk-js/src/DidHelpers/index.ts | 41 +---------------- .../sdk-js/src/DidHelpers/selectSigner.ts | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 40 deletions(-) create mode 100644 packages/sdk-js/src/DidHelpers/selectSigner.ts 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) +} From 9ad9820f954048d15ea9b447676338f8d0da9a3a Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 17 Jul 2024 14:09:35 +0200 Subject: [PATCH 37/70] refactor: streamline VM helper implementation --- packages/sdk-js/src/DidHelpers/common.ts | 22 ++-- packages/sdk-js/src/DidHelpers/createDid.ts | 15 ++- .../src/DidHelpers/verificationMethod.ts | 103 ++++++++---------- 3 files changed, 63 insertions(+), 77 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/common.ts b/packages/sdk-js/src/DidHelpers/common.ts index dc5d7bb65..46176612f 100644 --- a/packages/sdk-js/src/DidHelpers/common.ts +++ b/packages/sdk-js/src/DidHelpers/common.ts @@ -236,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 d87520bab..702d5d2ac 100644 --- a/packages/sdk-js/src/DidHelpers/createDid.ts +++ b/packages/sdk-js/src/DidHelpers/createDid.ts @@ -6,7 +6,12 @@ */ import { Blockchain } from '@kiltprotocol/chain-helpers' -import { getFullDid, getStoreTx, signingMethodTypes } from '@kiltprotocol/did' +import { + getFullDid, + getStoreTx, + type NewDidVerificationKey, + signingMethodTypes, +} from '@kiltprotocol/did' import type { KiltAddress, SignerInterface } from '@kiltprotocol/types' import { Crypto, Signers } from '@kiltprotocol/utils' @@ -38,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(`unknown key type ${keyType}`) + if (!signingMethodTypes.includes(type)) { + throw new Error(`unknown key type ${type}`) } const submitterAccount = ( 'address' in submitter ? submitter.address : submitter.id @@ -63,7 +68,7 @@ export function createDid( let didCreation = await getStoreTx( { - authentication: [{ publicKey, type: keyType as 'sr25519' }], + authentication: [{ publicKey, type } as NewDidVerificationKey], }, submitterAccount, accountSigners 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' From 51c2aa58ea679176d81a7e5446bceffff8f187f4 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 17 Jul 2024 14:17:43 +0200 Subject: [PATCH 38/70] fix: build error --- packages/sdk-js/src/DidHelpers/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk-js/src/DidHelpers/common.ts b/packages/sdk-js/src/DidHelpers/common.ts index 46176612f..266cd628b 100644 --- a/packages/sdk-js/src/DidHelpers/common.ts +++ b/packages/sdk-js/src/DidHelpers/common.ts @@ -41,7 +41,7 @@ function mapError(err: SpRuntimeDispatchError, api: ApiPromise): Error { return new Error(`${err.type}: ${err.value.toHuman()}`) } -function assertStatus(expected: string, actual: string): void { +function assertStatus(expected: string, actual?: string): void { if (actual !== expected) { const getterName = `as${expected.slice(0, 1).toUpperCase()}${expected.slice( 1 From 30d056091dfe9e0a9160e5a5ce654e70e0d49c7d Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 17 Jul 2024 15:10:16 +0200 Subject: [PATCH 39/70] test: integration test for transact --- tests/integration/didHelpers.spec.ts | 47 ++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/integration/didHelpers.spec.ts b/tests/integration/didHelpers.spec.ts index 776cfcae3..628734faa 100644 --- a/tests/integration/didHelpers.spec.ts +++ b/tests/integration/didHelpers.spec.ts @@ -7,6 +7,7 @@ import type { ApiPromise } from '@polkadot/api' +import { CType } from '@kiltprotocol/credentials' import { DidHelpers, disconnect } from '@kiltprotocol/sdk-js' import type { DidDocument, @@ -325,6 +326,52 @@ describe('verification methods', () => { }, 30_000) }) +describe('transact', () => { + let keypair: KeyringPair + let didDocument: DidDocument + beforeAll(async () => { + keypair = Crypto.makeKeypairFromUri('//Transact') + didDocument = ( + await DidHelpers.createDid({ + api, + signers: [keypair], + submitter: paymentAccount, + fromPublicKey: keypair, + }).submit() + ).asConfirmed.didDocument + + didDocument = ( + await DidHelpers.setVerificationMethod({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + publicKey: keypair, + relationship: 'assertionMethod', + }).submit() + ).asConfirmed.didDocument + }) + + it('creates a ctype', async () => { + const ctype = CType.fromProperties('thing', { thang: { type: 'string' } }) + const serialized = CType.toChain(ctype) + const call = api.tx.ctype.add(serialized) + + const result = await DidHelpers.transact({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + call, + expectedEvents: [{ section: 'ctype', method: 'CTypeCreated' }], + }).submit() + + expect(result.status).toStrictEqual('confirmed') + expect(result.asConfirmed.didDocument).toMatchObject(didDocument) + await expect(CType.verifyStored(ctype)).resolves.not.toThrow() + }, 30_000) +}) + afterAll(async () => { await disconnect() }) From 1dc7fc1fa87bc1653fe57063898ee10b20dec6cd Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Wed, 17 Jul 2024 20:26:13 +0200 Subject: [PATCH 40/70] chore: refactor `createDid` and `transact` tests --- packages/sdk-js/src/DidHelpers/common.ts | 3 +- .../sdk-js/src/DidHelpers/createDid.spec.ts | 86 +++++++++++++++++++ .../{index.spec.ts => transact.spec.ts} | 62 +------------ 3 files changed, 89 insertions(+), 62 deletions(-) create mode 100644 packages/sdk-js/src/DidHelpers/createDid.spec.ts rename packages/sdk-js/src/DidHelpers/{index.spec.ts => transact.spec.ts} (64%) diff --git a/packages/sdk-js/src/DidHelpers/common.ts b/packages/sdk-js/src/DidHelpers/common.ts index 266cd628b..15b061a0b 100644 --- a/packages/sdk-js/src/DidHelpers/common.ts +++ b/packages/sdk-js/src/DidHelpers/common.ts @@ -27,7 +27,6 @@ import type { Did, HexString } from '@kiltprotocol/types' import type { AcceptedPublicKeyEncodings, - KeyMultibaseEncoded, SharedArguments, TransactionHandlers, TransactionResult, @@ -245,7 +244,7 @@ export function convertPublicKey(pk: AcceptedPublicKeyEncodings): { ;({ publicKey, keyType: type } = multibaseKeyToDidKey(pk)) } else if ('publicKeyMultibase' in pk) { ;({ publicKey, keyType: type } = multibaseKeyToDidKey( - (pk as { publicKeyMultibase: KeyMultibaseEncoded }).publicKeyMultibase + pk.publicKeyMultibase )) } else if ( 'publicKey' in pk && diff --git a/packages/sdk-js/src/DidHelpers/createDid.spec.ts b/packages/sdk-js/src/DidHelpers/createDid.spec.ts new file mode 100644 index 000000000..3949d1e75 --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/createDid.spec.ts @@ -0,0 +1,86 @@ +/** + * 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 type { KiltKeyringPair } from '@kiltprotocol/types' +import { Crypto } from '@kiltprotocol/utils' +import { SubmittableResult } from '@polkadot/api' +import { resolver } from '@kiltprotocol/did' +import { + ApiMocks, + createLocalDemoFullDidFromKeypair, +} from '../../../../tests/testUtils/index.js' +import { ConfigService } from '../index.js' +import { createDid } from './createDid.js' +import { TransactionResult } from './interfaces.js' + +const mockedApi = ApiMocks.createAugmentedApi() +jest.mock('@kiltprotocol/did', () => { + return { + ...jest.requireActual('@kiltprotocol/did'), + resolver: { resolve: jest.fn() }, + authorizeTx: jest.fn(), + } +}) + +describe('createDid', () => { + let keypair: KiltKeyringPair + beforeAll(async () => { + ConfigService.set({ api: mockedApi }) + + keypair = Crypto.makeKeypairFromUri('//Alice') + const didDocument = await createLocalDemoFullDidFromKeypair(keypair) + jest.mocked(resolver).resolve.mockImplementation((did) => { + if (did === didDocument.id) { + return { didDocument } as any + } + throw new Error() + }) + }) + + it('creates create did tx', async () => { + const { txHex, checkResult } = await createDid({ + api: mockedApi, + submitter: keypair, + signers: [keypair], + fromPublicKey: keypair, + }).getSubmittable({ signSubmittable: false }) + + expect(txHex).toContain('0x') + const parsed = mockedApi.tx(txHex) + + const result = await checkResult( + new SubmittableResult({ + blockNumber: mockedApi.createType('BlockNumber', 1000), + status: mockedApi.createType('ExtrinsicStatus', { + inBlock: new Uint8Array(32).fill(2), + }), + txHash: parsed.hash, + events: [ + { + event: { + method: 'didCreated', + section: 'did', + }, + } as any, + ], + }) + ) + + expect(result).toMatchObject>({ + status: 'confirmed', + asConfirmed: expect.objectContaining({ + txHash: parsed.hash.toHex(), + block: { + hash: mockedApi + .createType('Hash', new Uint8Array(32).fill(2)) + .toHex(), + number: 1000n, + }, + }), + }) + }) +}) diff --git a/packages/sdk-js/src/DidHelpers/index.spec.ts b/packages/sdk-js/src/DidHelpers/transact.spec.ts similarity index 64% rename from packages/sdk-js/src/DidHelpers/index.spec.ts rename to packages/sdk-js/src/DidHelpers/transact.spec.ts index 6e768e431..b04961eb8 100644 --- a/packages/sdk-js/src/DidHelpers/index.spec.ts +++ b/packages/sdk-js/src/DidHelpers/transact.spec.ts @@ -16,7 +16,6 @@ import { } from '../../../../tests/testUtils/index.js' import { transact } from './index.js' import { TransactionResult } from './interfaces.js' -import { createDid } from './createDid.js' import { makeAttestationCreatedEvents } from '../../../../tests/testUtils/testData.js' jest.mock('@kiltprotocol/did', () => { @@ -36,22 +35,10 @@ describe('transact', () => { ConfigService.set({ api: mockedApi }) keypair = Crypto.makeKeypairFromUri('//Alice') - const { id, verificationMethod, authentication } = - await createLocalDemoFullDidFromKeypair(keypair, { - verificationRelationships: new Set(['assertionMethod']), - }) - didDocument = { - id, - authentication, - assertionMethod: authentication, - verificationMethod: verificationMethod?.filter( - (vm) => vm.id === authentication![0] - ), - } + didDocument = await createLocalDemoFullDidFromKeypair(keypair) jest .mocked(authorizeTx) - .mockImplementation(async (did, ex, sig, sub, { txCounter } = {}) => { - // if(!sig.find(i => i.id)) {} + .mockImplementation(async (_did, ex, _sig, sub, { txCounter } = {}) => { return mockedApi.tx.did.submitDidCall( { did: keypair.address, @@ -116,49 +103,4 @@ describe('transact', () => { }), }) }) - - it('create DID', async () => { - const { txHex, checkResult } = await ( - await createDid({ - api: mockedApi, - submitter: keypair, - signers: [keypair], - fromPublicKey: keypair, - }) - ).getSubmittable({ signSubmittable: false }) - - expect(txHex).toContain('0x') - const parsed = mockedApi.tx(txHex) - - const result = await checkResult( - new SubmittableResult({ - blockNumber: mockedApi.createType('BlockNumber', 1000), - status: mockedApi.createType('ExtrinsicStatus', { - inBlock: new Uint8Array(32).fill(2), - }), - txHash: parsed.hash, - events: [ - { - event: { - method: 'didCreated', - section: 'did', - }, - } as any, - ], - }) - ) - - expect(result).toMatchObject>({ - status: 'confirmed', - asConfirmed: expect.objectContaining({ - txHash: parsed.hash.toHex(), - block: { - hash: mockedApi - .createType('Hash', new Uint8Array(32).fill(2)) - .toHex(), - number: 1000n, - }, - }), - }) - }) }) From 6df788aa6975e00080c62b65ad47197e36884d59 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 17 Jul 2024 17:51:00 +0200 Subject: [PATCH 41/70] refactor: internal transact implementation with factory --- .../sdk-js/src/DidHelpers/deactivateDid.ts | 7 +- packages/sdk-js/src/DidHelpers/service.ts | 18 +-- .../sdk-js/src/DidHelpers/transact.spec.ts | 7 +- packages/sdk-js/src/DidHelpers/transact.ts | 33 ++++- .../src/DidHelpers/verificationMethod.ts | 132 +++++++++--------- packages/sdk-js/src/DidHelpers/w3names.ts | 10 +- 6 files changed, 111 insertions(+), 96 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/deactivateDid.ts b/packages/sdk-js/src/DidHelpers/deactivateDid.ts index ac6318d2b..1b684059f 100644 --- a/packages/sdk-js/src/DidHelpers/deactivateDid.ts +++ b/packages/sdk-js/src/DidHelpers/deactivateDid.ts @@ -6,7 +6,7 @@ */ import type { SharedArguments, TransactionHandlers } from './interfaces.js' -import { transact } from './transact.js' +import { transactInternal } from './transact.js' /** * _Permanently_ deactivates the DID, removing all verification methods and services from its document. @@ -18,9 +18,10 @@ import { transact } from './transact.js' */ export function deactivateDid(options: SharedArguments): TransactionHandlers { const { api, didDocument } = options - return transact({ + return transactInternal({ ...options, - call: api.tx.did.delete(didDocument.service?.length ?? 0), + callFactory: async () => + api.tx.did.delete(didDocument.service?.length ?? 0), expectedEvents: [{ section: 'did', method: 'DidDeleted' }], }) } diff --git a/packages/sdk-js/src/DidHelpers/service.ts b/packages/sdk-js/src/DidHelpers/service.ts index b2aee90dd..f6e346a26 100644 --- a/packages/sdk-js/src/DidHelpers/service.ts +++ b/packages/sdk-js/src/DidHelpers/service.ts @@ -8,7 +8,7 @@ import { serviceToChain, urlFragmentToChain } from '@kiltprotocol/did' import type { DidUrl, Service, UriFragment } from '@kiltprotocol/types' import { SharedArguments, TransactionHandlers } from './interfaces.js' -import { transact } from './transact.js' +import { transactInternal } from './transact.js' /** * Adds a service to the DID Document. @@ -23,12 +23,10 @@ export function addService( service: Service } ): TransactionHandlers { - const didServiceUpdateTx = options.api.tx.did.addServiceEndpoint( - serviceToChain(options.service) - ) - return transact({ + return transactInternal({ ...options, - call: didServiceUpdateTx, + callFactory: async () => + options.api.tx.did.addServiceEndpoint(serviceToChain(options.service)), expectedEvents: [{ section: 'did', method: 'DidUpdated' }], }) } @@ -46,12 +44,10 @@ export function removeService( id: DidUrl | UriFragment } ): TransactionHandlers { - const didServiceUpdateTx = options.api.tx.did.removeServiceEndpoint( - urlFragmentToChain(options.id) - ) - return transact({ + return transactInternal({ ...options, - call: didServiceUpdateTx, + callFactory: async () => + options.api.tx.did.removeServiceEndpoint(urlFragmentToChain(options.id)), expectedEvents: [{ section: 'did', method: 'DidUpdated' }], }) } diff --git a/packages/sdk-js/src/DidHelpers/transact.spec.ts b/packages/sdk-js/src/DidHelpers/transact.spec.ts index b04961eb8..f04299bea 100644 --- a/packages/sdk-js/src/DidHelpers/transact.spec.ts +++ b/packages/sdk-js/src/DidHelpers/transact.spec.ts @@ -5,18 +5,19 @@ * found in the LICENSE file in the root directory of this source tree. */ -import { SubmittableResult } from '@polkadot/api' import { authorizeTx, resolver } from '@kiltprotocol/did' import type { DidDocument, KiltKeyringPair } from '@kiltprotocol/types' import { Crypto } from '@kiltprotocol/utils' -import { ConfigService } from '../index.js' +import { SubmittableResult } from '@polkadot/api' + import { ApiMocks, createLocalDemoFullDidFromKeypair, } from '../../../../tests/testUtils/index.js' +import { makeAttestationCreatedEvents } from '../../../../tests/testUtils/testData.js' +import { ConfigService } from '../index.js' import { transact } from './index.js' import { TransactionResult } from './interfaces.js' -import { makeAttestationCreatedEvents } from '../../../../tests/testUtils/testData.js' jest.mock('@kiltprotocol/did', () => { return { diff --git a/packages/sdk-js/src/DidHelpers/transact.ts b/packages/sdk-js/src/DidHelpers/transact.ts index 40eb7b767..d8d6a8687 100644 --- a/packages/sdk-js/src/DidHelpers/transact.ts +++ b/packages/sdk-js/src/DidHelpers/transact.ts @@ -18,12 +18,12 @@ import type { SharedArguments, TransactionHandlers } from './interfaces.js' * Instructs a transaction (state transition) as this DID (with this DID as the origin). * * @param options Any {@link SharedArguments} and additional parameters. - * @param options.call The transaction / call to execute. + * @param options.callFactory Async callback producing the transaction / call to execute. * @returns A set of {@link TransactionHandlers}. */ -export function transact( +export function transactInternal( options: SharedArguments & { - call: Extrinsic | SubmittableExtrinsic + callFactory: () => Promise expectedEvents?: Array<{ section: string; method: string }> } ): TransactionHandlers { @@ -35,9 +35,16 @@ export function transact( } | undefined = {} ) => { - const { didDocument, signers, submitter, call, api, expectedEvents } = - options + const { + didDocument, + signers, + submitter, + callFactory, + api, + expectedEvents, + } = options const { didNonce, signSubmittable = true } = submitOptions + const call = await callFactory() const didSigners = await signersForDid(didDocument, ...signers) const submitterAccount = ( @@ -75,3 +82,19 @@ export function transact( getSubmittable, } } + +/** + * Instructs a transaction (state transition) as this DID (with this DID as the origin). + * + * @param options Any {@link SharedArguments} and additional parameters. + * @param options.call The transaction / call to execute. + * @returns A set of {@link TransactionHandlers}. + */ +export function transact( + options: SharedArguments & { + call: Extrinsic | SubmittableExtrinsic + expectedEvents?: Array<{ section: string; method: string }> + } +): TransactionHandlers { + return transactInternal({ ...options, callFactory: async () => options.call }) +} diff --git a/packages/sdk-js/src/DidHelpers/verificationMethod.ts b/packages/sdk-js/src/DidHelpers/verificationMethod.ts index e394ae15a..252d52a1b 100644 --- a/packages/sdk-js/src/DidHelpers/verificationMethod.ts +++ b/packages/sdk-js/src/DidHelpers/verificationMethod.ts @@ -23,7 +23,7 @@ import type { SharedArguments, TransactionHandlers, } from './interfaces.js' -import { transact } from './transact.js' +import { transactInternal } from './transact.js' /** * Replaces all existing verification methods for the selected `relationship` with `publicKey`. @@ -40,50 +40,47 @@ export function setVerificationMethod( relationship: VerificationRelationship } ): TransactionHandlers { - const { publicKey, relationship, didDocument, api } = options - const pk = convertPublicKey(publicKey) + const callFactory = async (): Promise => { + const { publicKey, relationship, didDocument, api } = options + const pk = convertPublicKey(publicKey) - 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) + switch (relationship) { + case 'keyAgreement': { + const txs: SubmittableExtrinsic[] = [] + didDocument.keyAgreement?.forEach((id) => + txs.push(api.tx.did.removeKeyAgreementKey(urlFragmentToChain(id))) ) - ) - didKeyUpdateTx = api.tx.utility.batchAll(txs) - break - } - case 'authentication': { - didKeyUpdateTx = api.tx.did.setAuthenticationKey( - publicKeyToChain(pk as NewDidVerificationKey) - ) - break - } - 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') + txs.push( + api.tx.did.addKeyAgreementKey( + publicKeyToChain(pk as NewDidEncryptionKey) + ) + ) + return api.tx.utility.batchAll(txs) + } + case 'authentication': { + return api.tx.did.setAuthenticationKey( + publicKeyToChain(pk as NewDidVerificationKey) + ) + } + case 'capabilityDelegation': { + return api.tx.did.setDelegationKey( + publicKeyToChain(pk as NewDidVerificationKey) + ) + } + case 'assertionMethod': { + return api.tx.did.setAttestationKey( + publicKeyToChain(pk as NewDidVerificationKey) + ) + } + default: { + throw new Error('unsupported relationship') + } } } - return transact({ + return transactInternal({ ...options, - call: didKeyUpdateTx, + callFactory, expectedEvents: [{ section: 'did', method: 'DidUpdated' }], }) } @@ -105,52 +102,49 @@ export function removeVerificationMethod( relationship: Omit } ): TransactionHandlers { - const { relationship, didDocument, api, verificationMethodId } = options - let didKeyUpdateTx - switch (relationship) { - case 'authentication': { - throw new Error('authentication verification methods can not be removed') - } - case 'capabilityDelegation': { - if (didDocument.capabilityDelegation?.includes(verificationMethodId)) { - didKeyUpdateTx = api.tx.did.removeDelegationKey() - } else { + const callFactory = async (): Promise => { + const { relationship, didDocument, api, verificationMethodId } = options + switch (relationship) { + case 'authentication': { throw new Error( - 'the specified capabilityDelegation method does not exist in the DID Document' + 'authentication verification methods can not be removed' ) } - break - } - case 'keyAgreement': { - if (didDocument.keyAgreement?.includes(verificationMethodId)) { - didKeyUpdateTx = api.tx.did.removeKeyAgreementKey( - urlFragmentToChain(verificationMethodId) + case 'capabilityDelegation': { + if (didDocument.capabilityDelegation?.includes(verificationMethodId)) { + return api.tx.did.removeDelegationKey() + } + throw new Error( + 'the specified capabilityDelegation method does not exist in the DID Document' ) - } else { + } + case 'keyAgreement': { + if (didDocument.keyAgreement?.includes(verificationMethodId)) { + return api.tx.did.removeKeyAgreementKey( + urlFragmentToChain(verificationMethodId) + ) + } throw new Error( 'the specified keyAgreement key does not exist in the DID Document' ) } - break - } - case 'assertionMethod': { - if (didDocument.assertionMethod?.includes(verificationMethodId)) { - didKeyUpdateTx = api.tx.did.removeAttestationKey() - } else { + case 'assertionMethod': { + if (didDocument.assertionMethod?.includes(verificationMethodId)) { + return api.tx.did.removeAttestationKey() + } throw new Error( 'the specified assertionMethod does not exist in the DID Document' ) } - break - } - default: { - throw new Error('the specified method relationship is not supported') + default: { + throw new Error('the specified method relationship is not supported') + } } } - return transact({ + return transactInternal({ ...options, - call: didKeyUpdateTx, + callFactory, expectedEvents: [{ section: 'did', method: 'DidUpdated' }], }) } diff --git a/packages/sdk-js/src/DidHelpers/w3names.ts b/packages/sdk-js/src/DidHelpers/w3names.ts index 0b58afdbd..5feeed056 100644 --- a/packages/sdk-js/src/DidHelpers/w3names.ts +++ b/packages/sdk-js/src/DidHelpers/w3names.ts @@ -6,7 +6,7 @@ */ import type { SharedArguments, TransactionHandlers } from './interfaces.js' -import { transact } from './transact.js' +import { transactInternal } from './transact.js' /** * Adds a w3n nickname to the DID Document. @@ -22,9 +22,9 @@ export function claimWeb3Name( } ): TransactionHandlers { const { api, name } = options - return transact({ + return transactInternal({ ...options, - call: api.tx.web3Names.claim(name), + callFactory: async () => api.tx.web3Names.claim(name), expectedEvents: [{ section: 'web3Names', method: 'Web3NameClaimed' }], }) } @@ -37,9 +37,9 @@ export function claimWeb3Name( */ export function releaseWeb3Name(options: SharedArguments): TransactionHandlers { const { api } = options - return transact({ + return transactInternal({ ...options, - call: api.tx.web3Names.releaseByOwner(), + callFactory: async () => api.tx.web3Names.releaseByOwner(), expectedEvents: [{ section: 'web3Names', method: 'Web3NameReleased' }], }) } From 455d96f50918b06bebe14afa40c719652c01d6d9 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Thu, 18 Jul 2024 09:51:01 +0200 Subject: [PATCH 42/70] test: fix unit tests --- .../src/DidHelpers/deactivateDid.spec.ts | 14 +++-- .../sdk-js/src/DidHelpers/service.spec.ts | 28 ++++++--- .../sdk-js/src/DidHelpers/transact.spec.ts | 2 +- .../src/DidHelpers/verificationMethod.spec.ts | 58 +++++++++++++------ .../sdk-js/src/DidHelpers/w3names.spec.ts | 25 +++++--- 5 files changed, 86 insertions(+), 41 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/deactivateDid.spec.ts b/packages/sdk-js/src/DidHelpers/deactivateDid.spec.ts index 96a7e9a96..c42c395a8 100644 --- a/packages/sdk-js/src/DidHelpers/deactivateDid.spec.ts +++ b/packages/sdk-js/src/DidHelpers/deactivateDid.spec.ts @@ -13,11 +13,11 @@ import { } from '../../../../tests/testUtils/index.js' import { ConfigService } from '../index.js' import { deactivateDid } from './deactivateDid.js' -import { transact } from './transact.js' +import { transactInternal } from './transact.js' jest.mock('./transact.js') -const mockedTransact = jest.mocked(transact) +const mockedTransact = jest.mocked(transactInternal) const mockedApi = ApiMocks.createAugmentedApi() describe('deactivate', () => { @@ -50,8 +50,8 @@ describe('deactivate', () => { }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -64,7 +64,11 @@ describe('deactivate', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { method: 'delete', section: 'did' }, }) }) diff --git a/packages/sdk-js/src/DidHelpers/service.spec.ts b/packages/sdk-js/src/DidHelpers/service.spec.ts index 0e5b7f91c..b2d79712a 100644 --- a/packages/sdk-js/src/DidHelpers/service.spec.ts +++ b/packages/sdk-js/src/DidHelpers/service.spec.ts @@ -12,12 +12,12 @@ import { createLocalDemoFullDidFromKeypair, } from '../../../../tests/testUtils/index.js' import { ConfigService } from '../index.js' -import { transact } from './index.js' import { addService, removeService } from './service.js' +import { transactInternal } from './transact.js' jest.mock('./transact.js') -const mockedTransact = jest.mocked(transact) +const mockedTransact = jest.mocked(transactInternal) const mockedApi = ApiMocks.createAugmentedApi() describe.each(['#my_service', 'did:kilt:4abctest#my_service'])( @@ -57,8 +57,10 @@ describe.each(['#my_service', 'did:kilt:4abctest#my_service'])( }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining< + Partial[0]> + >({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -71,7 +73,11 @@ describe.each(['#my_service', 'did:kilt:4abctest#my_service'])( signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { args: { service_endpoint: { @@ -96,8 +102,10 @@ describe.each(['#my_service', 'did:kilt:4abctest#my_service'])( }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining< + Partial[0]> + >({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -110,7 +118,11 @@ describe.each(['#my_service', 'did:kilt:4abctest#my_service'])( signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { args: { service_id: 'my_service', diff --git a/packages/sdk-js/src/DidHelpers/transact.spec.ts b/packages/sdk-js/src/DidHelpers/transact.spec.ts index f04299bea..378eb1f27 100644 --- a/packages/sdk-js/src/DidHelpers/transact.spec.ts +++ b/packages/sdk-js/src/DidHelpers/transact.spec.ts @@ -16,8 +16,8 @@ import { } from '../../../../tests/testUtils/index.js' import { makeAttestationCreatedEvents } from '../../../../tests/testUtils/testData.js' import { ConfigService } from '../index.js' -import { transact } from './index.js' import { TransactionResult } from './interfaces.js' +import { transact } from './transact.js' jest.mock('@kiltprotocol/did', () => { return { diff --git a/packages/sdk-js/src/DidHelpers/verificationMethod.spec.ts b/packages/sdk-js/src/DidHelpers/verificationMethod.spec.ts index 5c5e2c130..9e468aa24 100644 --- a/packages/sdk-js/src/DidHelpers/verificationMethod.spec.ts +++ b/packages/sdk-js/src/DidHelpers/verificationMethod.spec.ts @@ -12,15 +12,15 @@ import { createLocalDemoFullDidFromKeypair, } from '../../../../tests/testUtils/index.js' import { ConfigService } from '../index.js' +import { transactInternal } from './transact.js' import { removeVerificationMethod, setVerificationMethod, } from './verificationMethod.js' -import { transact } from './transact.js' jest.mock('./transact.js') -const mockedTransact = jest.mocked(transact) +const mockedTransact = jest.mocked(transactInternal) const mockedApi = ApiMocks.createAugmentedApi() let didDocument: DidDocument @@ -55,8 +55,8 @@ describe('signing keys', () => { }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -69,7 +69,11 @@ describe('signing keys', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { section: 'did', method: 'setAttestationKey', @@ -90,8 +94,8 @@ describe('signing keys', () => { }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -104,7 +108,11 @@ describe('signing keys', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { section: 'did', method: 'removeAttestationKey', @@ -120,13 +128,13 @@ describe('key agreement keys', () => { api: mockedApi, submitter: keypair, signers: [keypair], - publicKey: { publicKey: keypair.publicKey, type: 'x25519' } as any, + publicKey: { publicKey: keypair.publicKey, type: 'x25519' }, relationship: 'keyAgreement', }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -139,7 +147,11 @@ describe('key agreement keys', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { section: 'utility', method: 'batchAll', @@ -165,13 +177,13 @@ describe('key agreement keys', () => { api: mockedApi, submitter: keypair, signers: [keypair], - publicKey: { publicKey: keypair.publicKey, type: 'x25519' } as any, + publicKey: { publicKey: keypair.publicKey, type: 'x25519' }, relationship: 'keyAgreement', }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -184,7 +196,11 @@ describe('key agreement keys', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { section: 'utility', method: 'batchAll', @@ -217,8 +233,8 @@ describe('key agreement keys', () => { }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -231,7 +247,11 @@ describe('key agreement keys', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { section: 'did', method: 'removeKeyAgreementKey', diff --git a/packages/sdk-js/src/DidHelpers/w3names.spec.ts b/packages/sdk-js/src/DidHelpers/w3names.spec.ts index e078b5d4d..55f625e51 100644 --- a/packages/sdk-js/src/DidHelpers/w3names.spec.ts +++ b/packages/sdk-js/src/DidHelpers/w3names.spec.ts @@ -12,11 +12,12 @@ import { createLocalDemoFullDidFromKeypair, } from '../../../../tests/testUtils/index.js' import { ConfigService } from '../index.js' -import { claimWeb3Name, releaseWeb3Name, transact } from './index.js' +import { transactInternal } from './transact.js' +import { claimWeb3Name, releaseWeb3Name } from './w3names.js' jest.mock('./transact.js') -const mockedTransact = jest.mocked(transact) +const mockedTransact = jest.mocked(transactInternal) const mockedApi = ApiMocks.createAugmentedApi() describe('w3n', () => { @@ -50,8 +51,8 @@ describe('w3n', () => { }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'web3Names', @@ -64,7 +65,11 @@ describe('w3n', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { args: { name: 'paul' }, method: 'claim', section: 'web3Names' }, }) }) @@ -78,8 +83,8 @@ describe('w3n', () => { }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'web3Names', @@ -92,7 +97,11 @@ describe('w3n', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { method: 'releaseByOwner', section: 'web3Names' }, }) }) From 9e1b6b4c77d965b8ff058574b43199df4c9945f9 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Thu, 18 Jul 2024 13:11:06 +0200 Subject: [PATCH 43/70] chore: refactor checkResult --- packages/sdk-js/src/DidHelpers/checkResult.ts | 240 ++++++++++++++++++ packages/sdk-js/src/DidHelpers/common.ts | 182 +------------ packages/sdk-js/src/DidHelpers/createDid.ts | 3 +- packages/sdk-js/src/DidHelpers/transact.ts | 3 +- 4 files changed, 248 insertions(+), 180 deletions(-) create mode 100644 packages/sdk-js/src/DidHelpers/checkResult.ts diff --git a/packages/sdk-js/src/DidHelpers/checkResult.ts b/packages/sdk-js/src/DidHelpers/checkResult.ts new file mode 100644 index 000000000..03ca7cfa5 --- /dev/null +++ b/packages/sdk-js/src/DidHelpers/checkResult.ts @@ -0,0 +1,240 @@ +/** + * 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. + */ + +/* eslint-disable jsdoc/require-jsdoc */ +// functions in this file are not meant to be public + +import type { ApiPromise } from '@polkadot/api' +import type { SubmittableResultValue } from '@polkadot/api/types' +import type { BlockNumber } from '@polkadot/types/interfaces' +import { u8aToHex, u8aToU8a } from '@polkadot/util' +import type { FrameSystemEventRecord as EventRecord } from '@kiltprotocol/augment-api' +import { resolver as DidResolver, signersForDid } from '@kiltprotocol/did' +import type { Did, HexString } from '@kiltprotocol/types' +import type { SharedArguments, TransactionResult } from './interfaces.js' +import { assertStatus, mapError } from './common.js' + +function checkEventsForErrors( + api: ApiPromise, + txEvents: EventRecord[] = [] +): Error | undefined { + let error + txEvents.forEach(({ event }) => { + if (api.events.system.ExtrinsicFailed.is(event)) { + error = mapError(event.data[0], api) + } else if (api.events.proxy.ProxyExecuted.is(event)) { + const res = event.data[0] + if (res.isErr) { + error = mapError(res.asErr, api) + } + } else if (api.events.did.DidCallDispatched.is(event)) { + const res = event.data[1] + if (res.isErr) { + error = mapError(res.asErr, api) + } + } else if (api.events.utility.ItemFailed.is(event)) { + error = mapError(event.data[0], api) + } else if (api.events.utility.BatchInterrupted.is(event)) { + error = mapError(event.data[1], api) + } + }) + return error +} + +// Note: returns `undefined` in the case of `status === "inBlock`. +function checkStatus(result: SubmittableResultValue): { + status: TransactionResult['status'] | undefined + error: Error | undefined + blockHash: HexString | undefined + blockNumber: BigInt | undefined +} { + let status: TransactionResult['status'] | undefined + let blockHash + let blockNumber + let error + switch (result.status.type) { + case 'Finalized': + case 'InBlock': + // status must not be set here; this condition triggers a branch below + // this is the block hash for both + status = undefined + blockHash = result.status.value.toHex() + if ('blockNumber' in result) { + blockNumber = (result.blockNumber as BlockNumber).toBigInt() + } + break + case 'Dropped': + case 'FinalityTimeout': + case 'Invalid': + case 'Usurped': + status = 'rejected' + error = new Error(result.status.type) + break + case 'Broadcast': + case 'Future': + case 'Ready': + case 'Retracted': + status = 'unknown' + error = new Error(result.status.type) + break + default: + status = 'unknown' + error = new Error(`unknown tx status variant ${result.status.type}`) + } + + return { + status, + error, + blockHash, + blockNumber, + } +} + +async function resolveBlockAndEvents( + result: { blockHash: HexString; txHash: HexString }, + api: ApiPromise +): Promise<{ + blockNumber: BigInt + blockHash: HexString + txEvents: EventRecord[] +}> { + const txHashHash = api.createType('Hash', result.blockHash) + const { + block: { block }, + events, + } = await api.derive.tx.events(txHashHash) + const blockNumber = block.header.number.toBigInt() + const { blockHash } = result + const txIndex = block.extrinsics.findIndex((tx) => tx.hash.eq(result.txHash)) + const txEvents = events.filter( + ({ phase }) => phase.isApplyExtrinsic && phase.asApplyExtrinsic.eqn(txIndex) + ) + return { blockNumber, blockHash, txEvents } +} + +function checkIfEventsMatch( + expectedEvents: Array<{ section: string; method: string }>, + txEvents: EventRecord[] +): boolean { + return expectedEvents.every(({ section, method }) => + txEvents.some( + ({ event }) => + event.section.toLowerCase() === section.toLowerCase() && + event.method.toLowerCase() === method.toLowerCase() + ) + ) +} + +export async function checkResultImpl( + result: { blockHash: HexString; txHash: HexString } | SubmittableResultValue, + api: ApiPromise, + expectedEvents: Array<{ section: string; method: string }> = [], + did: Did, + signersOrKeys: SharedArguments['signers'] +): Promise { + let txEvents: EventRecord[] = [] + let status: TransactionResult['status'] | undefined + let error: Error | undefined + let blockHash: HexString | undefined + let blockNumber: BigInt | undefined + + // Case where `SubmittableResultValue` is provided. + if ('status' in result) { + ;({ status, blockHash, blockNumber, error } = checkStatus(result)) + } + // Case where block hash and tx hash are provided. + else if ( + 'blockHash' in result && + 'txHash' in result && + typeof result.blockHash === 'string' && + typeof result.txHash === 'string' + ) { + // Set blockNumber, blockHash, and the transactionEvents. + ;({ blockNumber, blockHash, txEvents } = await resolveBlockAndEvents( + result, + api + )) + } + // Type of the `result` argument is invalid. + else { + status = 'unknown' + error = new Error('missing blockHash and/or txHash') + } + + if (typeof status !== 'string') { + const eventError = checkEventsForErrors(api, txEvents) + if (eventError !== undefined) { + error = eventError + } + + const eventMatch = checkIfEventsMatch(expectedEvents, txEvents) + const isSuccess = !error && eventMatch + + status = isSuccess ? 'confirmed' : 'failed' + if (!isSuccess && !error) { + error = new Error('did not find expected events') + } + } + + const { didDocument } = await DidResolver.resolve(did) + if (blockHash && !blockNumber) { + blockNumber = ( + await (await api.at(blockHash)).query.system.number() + ).toBigInt() + } + 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'] { + assertStatus('failed', status) + return { + 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'] { + assertStatus('unknown', status) + return { + error: error as Error, + txHash: u8aToHex(u8aToU8a(result.txHash)), + } + }, + get asRejected(): TransactionResult['asRejected'] { + assertStatus('rejected', status) + return { + error: error ?? new Error('unknown error'), + txHash: u8aToHex(u8aToU8a(result.txHash)), + signers, + didDocument, + } + }, + get asConfirmed(): TransactionResult['asConfirmed'] { + 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), + } + }, + } +} diff --git a/packages/sdk-js/src/DidHelpers/common.ts b/packages/sdk-js/src/DidHelpers/common.ts index 15b061a0b..6d413f9be 100644 --- a/packages/sdk-js/src/DidHelpers/common.ts +++ b/packages/sdk-js/src/DidHelpers/common.ts @@ -9,30 +9,17 @@ // functions in this file are not meant to be public import type { ApiPromise } from '@polkadot/api' -import type { SubmittableResultValue } from '@polkadot/api/types' -import type { BlockNumber } from '@polkadot/types/interfaces' -import { u8aToHex, u8aToU8a } from '@polkadot/util' - -import type { - FrameSystemEventRecord as EventRecord, - SpRuntimeDispatchError, -} from '@kiltprotocol/augment-api' +import type { SpRuntimeDispatchError } from '@kiltprotocol/augment-api' import { Blockchain } from '@kiltprotocol/chain-helpers' -import { - resolver as DidResolver, - multibaseKeyToDidKey, - signersForDid, -} from '@kiltprotocol/did' -import type { Did, HexString } from '@kiltprotocol/types' +import { multibaseKeyToDidKey } from '@kiltprotocol/did' import type { AcceptedPublicKeyEncodings, SharedArguments, TransactionHandlers, - TransactionResult, } from './interfaces.js' -function mapError(err: SpRuntimeDispatchError, api: ApiPromise): Error { +export function mapError(err: SpRuntimeDispatchError, api: ApiPromise): Error { if (err.isModule) { const { docs, method, section } = api.registry.findMetaError(err.asModule) return new Error(`${section}.${method}: ${docs}`) @@ -40,7 +27,7 @@ function mapError(err: SpRuntimeDispatchError, api: ApiPromise): Error { return new Error(`${err.type}: ${err.value.toHuman()}`) } -function assertStatus(expected: string, actual?: string): void { +export function assertStatus(expected: string, actual?: string): void { if (actual !== expected) { const getterName = `as${expected.slice(0, 1).toUpperCase()}${expected.slice( 1 @@ -49,167 +36,6 @@ function assertStatus(expected: string, actual?: string): void { } } -export async function checkResultImpl( - result: { blockHash: HexString; txHash: HexString } | SubmittableResultValue, - api: ApiPromise, - expectedEvents: Array<{ section: string; method: string }> = [], - did: Did, - signersOrKeys: SharedArguments['signers'] -): Promise { - let txEvents: EventRecord[] = [] - let status: TransactionResult['status'] | undefined - let error: Error | undefined - let blockHash: HexString | undefined - let blockNumber: BigInt | undefined - if ('status' in result) { - txEvents = result.events ?? [] - switch (result.status.type) { - case 'Finalized': - case 'InBlock': - // status must not be set here; this condition triggers a branch below - // this is the block hash for both - blockHash = result.status.value.toHex() - if ('blockNumber' in result) { - blockNumber = (result.blockNumber as BlockNumber).toBigInt() - } - break - case 'Dropped': - case 'FinalityTimeout': - case 'Invalid': - case 'Usurped': - status = 'rejected' - error = new Error(result.status.type) - break - case 'Broadcast': - case 'Future': - case 'Ready': - case 'Retracted': - status = 'unknown' - error = new Error(result.status.type) - break - default: - status = 'unknown' - error = new Error(`unknown tx status variant ${result.status.type}`) - } - } else if ( - 'blockHash' in result && - 'txHash' in result && - typeof result.blockHash === 'string' && - typeof result.txHash === 'string' - ) { - const txHashHash = api.createType('Hash', result.blockHash) - const { - block: { block }, - events, - } = await api.derive.tx.events(txHashHash) - blockNumber = block.header.number.toBigInt() - blockHash = result.blockHash - const txIndex = block.extrinsics.findIndex((tx) => - tx.hash.eq(result.txHash) - ) - txEvents = events.filter( - ({ phase }) => - phase.isApplyExtrinsic && phase.asApplyExtrinsic.eqn(txIndex) - ) - } else { - status = 'unknown' - error = new Error('missing blockHash and/or txHash') - } - if (typeof status !== 'string') { - txEvents.forEach(({ event }) => { - if (api.events.system.ExtrinsicFailed.is(event)) { - error = mapError(event.data[0], api) - } else if (api.events.proxy.ProxyExecuted.is(event)) { - const res = event.data[0] - if (res.isErr) { - error = mapError(res.asErr, api) - } - } else if (api.events.did.DidCallDispatched.is(event)) { - const res = event.data[1] - if (res.isErr) { - error = mapError(res.asErr, api) - } - } else if (api.events.utility.ItemFailed.is(event)) { - error = mapError(event.data[0], api) - } else if (api.events.utility.BatchInterrupted.is(event)) { - error = mapError(event.data[1], api) - } - }) - const eventMatch = expectedEvents.every(({ section, method }) => - txEvents.some( - ({ event }) => - event.section.toLowerCase() === section.toLowerCase() && - event.method.toLowerCase() === method.toLowerCase() - ) - ) - - const isSuccess = !error && eventMatch - - status = isSuccess ? 'confirmed' : 'failed' - if (!isSuccess && !error) { - error = new Error('did not find expected events') - } - } - - const { didDocument } = await DidResolver.resolve(did) - if (blockHash && !blockNumber) { - blockNumber = ( - await (await api.at(blockHash)).query.system.number() - ).toBigInt() - } - 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'] { - assertStatus('failed', status) - return { - 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'] { - assertStatus('unknown', status) - return { - error: error as Error, - txHash: u8aToHex(u8aToU8a(result.txHash)), - } - }, - get asRejected(): TransactionResult['asRejected'] { - assertStatus('rejected', status) - return { - error: error ?? new Error('unknown error'), - txHash: u8aToHex(u8aToU8a(result.txHash)), - signers, - didDocument, - } - }, - get asConfirmed(): TransactionResult['asConfirmed'] { - 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), - } - }, - } -} - export async function submitImpl( getSubmittable: TransactionHandlers['getSubmittable'], options: Pick & { diff --git a/packages/sdk-js/src/DidHelpers/createDid.ts b/packages/sdk-js/src/DidHelpers/createDid.ts index 702d5d2ac..285fd5472 100644 --- a/packages/sdk-js/src/DidHelpers/createDid.ts +++ b/packages/sdk-js/src/DidHelpers/createDid.ts @@ -14,8 +14,9 @@ import { } from '@kiltprotocol/did' import type { KiltAddress, SignerInterface } from '@kiltprotocol/types' import { Crypto, Signers } from '@kiltprotocol/utils' +import { checkResultImpl } from './checkResult.js' -import { checkResultImpl, convertPublicKey, submitImpl } from './common.js' +import { convertPublicKey, submitImpl } from './common.js' import type { AcceptedPublicKeyEncodings, SharedArguments, diff --git a/packages/sdk-js/src/DidHelpers/transact.ts b/packages/sdk-js/src/DidHelpers/transact.ts index d8d6a8687..393f32bf7 100644 --- a/packages/sdk-js/src/DidHelpers/transact.ts +++ b/packages/sdk-js/src/DidHelpers/transact.ts @@ -11,8 +11,9 @@ import { Blockchain } from '@kiltprotocol/chain-helpers' import { authorizeTx, signersForDid } from '@kiltprotocol/did' import type { KiltAddress, SubmittableExtrinsic } from '@kiltprotocol/types' -import { checkResultImpl, submitImpl } from './common.js' +import { submitImpl } from './common.js' import type { SharedArguments, TransactionHandlers } from './interfaces.js' +import { checkResultImpl } from './checkResult.js' /** * Instructs a transaction (state transition) as this DID (with this DID as the origin). From 7484bd522137a5efc604f4142f47c9a913432e69 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Thu, 18 Jul 2024 13:24:01 +0200 Subject: [PATCH 44/70] fix: set events --- packages/sdk-js/src/DidHelpers/checkResult.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sdk-js/src/DidHelpers/checkResult.ts b/packages/sdk-js/src/DidHelpers/checkResult.ts index 03ca7cfa5..5634ef764 100644 --- a/packages/sdk-js/src/DidHelpers/checkResult.ts +++ b/packages/sdk-js/src/DidHelpers/checkResult.ts @@ -144,6 +144,7 @@ export async function checkResultImpl( // Case where `SubmittableResultValue` is provided. if ('status' in result) { + txEvents = result.events ?? [] ;({ status, blockHash, blockNumber, error } = checkStatus(result)) } // Case where block hash and tx hash are provided. From 331dd5119c8cc1ee28076c2167b57215af570e79 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Thu, 14 Dec 2023 18:48:22 +0100 Subject: [PATCH 45/70] chore: set versions --- package.json | 2 +- packages/asset-credentials/package.json | 2 +- packages/chain-helpers/package.json | 2 +- packages/config/package.json | 2 +- packages/credentials/package.json | 2 +- packages/did/package.json | 2 +- packages/jsonld-suites/package.json | 2 +- packages/legacy-credentials/package.json | 2 +- packages/sdk-js/package.json | 2 +- packages/types/package.json | 2 +- packages/utils/package.json | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 45f792d78..cb9130291 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,6 @@ "typedoc": "^0.24.8", "typescript": "^4.8.3" }, - "version": "0.34.0", + "version": "1.0.0-alpha.1", "packageManager": "yarn@4.1.1" } diff --git a/packages/asset-credentials/package.json b/packages/asset-credentials/package.json index 54a6b1795..a16a02319 100644 --- a/packages/asset-credentials/package.json +++ b/packages/asset-credentials/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/asset-credentials", - "version": "0.34.0", + "version": "0.100.0-alpha.1", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/chain-helpers/package.json b/packages/chain-helpers/package.json index f733c5818..f8d82da43 100644 --- a/packages/chain-helpers/package.json +++ b/packages/chain-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/chain-helpers", - "version": "0.34.0", + "version": "0.100.0-alpha.1", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/config/package.json b/packages/config/package.json index 3e54bf639..11f44ef6a 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/config", - "version": "0.34.0", + "version": "0.100.0-alpha.1", "description": "", "type": "commonjs", "main": "./lib/index.js", diff --git a/packages/credentials/package.json b/packages/credentials/package.json index 0d44f5c66..337abc0fc 100644 --- a/packages/credentials/package.json +++ b/packages/credentials/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/credentials", - "version": "0.34.0", + "version": "0.100.0-alpha.1", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/did/package.json b/packages/did/package.json index cf974958c..08a2fd209 100644 --- a/packages/did/package.json +++ b/packages/did/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/did", - "version": "0.34.0", + "version": "0.100.0-alpha.1", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/jsonld-suites/package.json b/packages/jsonld-suites/package.json index b11a4eae6..d2ed2da78 100644 --- a/packages/jsonld-suites/package.json +++ b/packages/jsonld-suites/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/jsonld-suites", - "version": "0.34.0", + "version": "0.100.0-alpha.1", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/legacy-credentials/package.json b/packages/legacy-credentials/package.json index 132bb2863..64d9745d4 100644 --- a/packages/legacy-credentials/package.json +++ b/packages/legacy-credentials/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/legacy-credentials", - "version": "0.33.2-6", + "version": "0.100.0-alpha.1", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/sdk-js/package.json b/packages/sdk-js/package.json index eb46054f3..623971dcd 100644 --- a/packages/sdk-js/package.json +++ b/packages/sdk-js/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/sdk-js", - "version": "0.34.0", + "version": "1.0.0-alpha.1", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/types/package.json b/packages/types/package.json index df3c0799c..c831b7b3a 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/types", - "version": "0.34.0", + "version": "0.100.0-alpha.1", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/utils/package.json b/packages/utils/package.json index aaf882a60..dfa821e6a 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/utils", - "version": "0.34.0", + "version": "0.100.0-alpha.1", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", From eda130f864f7bf92387649cd20ec9f3cc28d2352 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Thu, 18 Jul 2024 18:37:50 +0200 Subject: [PATCH 46/70] chore(deps): bump jcs versions --- packages/credentials/package.json | 8 +- packages/did/package.json | 2 +- packages/utils/package.json | 6 +- yarn.lock | 156 ++++++------------------------ 4 files changed, 37 insertions(+), 135 deletions(-) diff --git a/packages/credentials/package.json b/packages/credentials/package.json index 337abc0fc..242662d65 100644 --- a/packages/credentials/package.json +++ b/packages/credentials/package.json @@ -41,10 +41,10 @@ "@kiltprotocol/chain-helpers": "workspace:*", "@kiltprotocol/config": "workspace:*", "@kiltprotocol/did": "workspace:*", - "@kiltprotocol/eddsa-jcs-2022": "0.1.0-rc.3", - "@kiltprotocol/es256k-jcs-2023": "0.1.0-rc.3", - "@kiltprotocol/jcs-data-integrity-proofs-common": "0.1.0-rc.3", - "@kiltprotocol/sr25519-jcs-2023": "0.1.0-rc.3", + "@kiltprotocol/eddsa-jcs-2022": "0.1.0-rc.4", + "@kiltprotocol/es256k-jcs-2023": "0.1.0-rc.4", + "@kiltprotocol/jcs-data-integrity-proofs-common": "0.1.0-rc.4", + "@kiltprotocol/sr25519-jcs-2023": "0.1.0-rc.4", "@kiltprotocol/types": "workspace:*", "@kiltprotocol/utils": "workspace:*", "@polkadot/api": "^12.0.0", diff --git a/packages/did/package.json b/packages/did/package.json index 08a2fd209..930af0ebe 100644 --- a/packages/did/package.json +++ b/packages/did/package.json @@ -40,7 +40,7 @@ "@digitalbazaar/multikey-context": "^2.0.1", "@digitalbazaar/security-context": "^1.0.1", "@kiltprotocol/config": "workspace:*", - "@kiltprotocol/jcs-data-integrity-proofs-common": "0.1.0-rc.3", + "@kiltprotocol/jcs-data-integrity-proofs-common": "0.1.0-rc.4", "@kiltprotocol/types": "workspace:*", "@kiltprotocol/utils": "workspace:*", "@polkadot/api": "^12.0.0", diff --git a/packages/utils/package.json b/packages/utils/package.json index dfa821e6a..ad0fcdd1c 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -35,9 +35,9 @@ "typescript": "^4.8.3" }, "dependencies": { - "@kiltprotocol/eddsa-jcs-2022": "0.1.0-rc.3", - "@kiltprotocol/es256k-jcs-2023": "0.1.0-rc.3", - "@kiltprotocol/sr25519-jcs-2023": "0.1.0-rc.3", + "@kiltprotocol/eddsa-jcs-2022": "0.1.0-rc.4", + "@kiltprotocol/es256k-jcs-2023": "0.1.0-rc.4", + "@kiltprotocol/sr25519-jcs-2023": "0.1.0-rc.4", "@kiltprotocol/types": "workspace:*", "@polkadot/api": "^12.0.0", "@polkadot/keyring": "^13.0.0", diff --git a/yarn.lock b/yarn.lock index 005cd363b..fd5ee1074 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2072,10 +2072,10 @@ __metadata: "@kiltprotocol/chain-helpers": "workspace:*" "@kiltprotocol/config": "workspace:*" "@kiltprotocol/did": "workspace:*" - "@kiltprotocol/eddsa-jcs-2022": "npm:0.1.0-rc.3" - "@kiltprotocol/es256k-jcs-2023": "npm:0.1.0-rc.3" - "@kiltprotocol/jcs-data-integrity-proofs-common": "npm:0.1.0-rc.3" - "@kiltprotocol/sr25519-jcs-2023": "npm:0.1.0-rc.3" + "@kiltprotocol/eddsa-jcs-2022": "npm:0.1.0-rc.4" + "@kiltprotocol/es256k-jcs-2023": "npm:0.1.0-rc.4" + "@kiltprotocol/jcs-data-integrity-proofs-common": "npm:0.1.0-rc.4" + "@kiltprotocol/sr25519-jcs-2023": "npm:0.1.0-rc.4" "@kiltprotocol/types": "workspace:*" "@kiltprotocol/utils": "workspace:*" "@polkadot/api": "npm:^12.0.0" @@ -2102,7 +2102,7 @@ __metadata: "@digitalbazaar/multikey-context": "npm:^2.0.1" "@digitalbazaar/security-context": "npm:^1.0.1" "@kiltprotocol/config": "workspace:*" - "@kiltprotocol/jcs-data-integrity-proofs-common": "npm:0.1.0-rc.3" + "@kiltprotocol/jcs-data-integrity-proofs-common": "npm:0.1.0-rc.4" "@kiltprotocol/types": "workspace:*" "@kiltprotocol/utils": "workspace:*" "@polkadot/api": "npm:^12.0.0" @@ -2121,38 +2121,38 @@ __metadata: languageName: unknown linkType: soft -"@kiltprotocol/eddsa-jcs-2022@npm:0.1.0-rc.3": - version: 0.1.0-rc.3 - resolution: "@kiltprotocol/eddsa-jcs-2022@npm:0.1.0-rc.3" +"@kiltprotocol/eddsa-jcs-2022@npm:0.1.0-rc.4": + version: 0.1.0-rc.4 + resolution: "@kiltprotocol/eddsa-jcs-2022@npm:0.1.0-rc.4" dependencies: - "@kiltprotocol/jcs-data-integrity-proofs-common": "npm:^0.1.0-rc.3" + "@kiltprotocol/jcs-data-integrity-proofs-common": "npm:^0.1.0-rc.4" "@noble/curves": "npm:^1.0.0" "@scure/base": "npm:^1.1.1" - checksum: 10c0/b29228114faf0322bf21a372c2c4c99379d9d9006bb9a94a7029211bce47321f185ea2fe8182499ac4bcdaa5dd4aad0eafccea1e22cfbcb16058be17a0c3c164 + checksum: 10c0/a4a974e225cb90d17ca1902e6dc40bb0b71826d0d898ed4bad2c10be72938908fa480cfba040badf2754778a8e78843ce4262110c3360138d1f5aff1527f2a42 languageName: node linkType: hard -"@kiltprotocol/es256k-jcs-2023@npm:0.1.0-rc.3": - version: 0.1.0-rc.3 - resolution: "@kiltprotocol/es256k-jcs-2023@npm:0.1.0-rc.3" +"@kiltprotocol/es256k-jcs-2023@npm:0.1.0-rc.4": + version: 0.1.0-rc.4 + resolution: "@kiltprotocol/es256k-jcs-2023@npm:0.1.0-rc.4" dependencies: - "@kiltprotocol/jcs-data-integrity-proofs-common": "npm:^0.1.0-rc.3" + "@kiltprotocol/jcs-data-integrity-proofs-common": "npm:^0.1.0-rc.4" "@noble/curves": "npm:^1.0.0" "@scure/base": "npm:^1.1.1" - checksum: 10c0/02347bc7f199833b35995f89f4c13b02211ada54707f086896f841dd64bee1edf332c4e5139343d3d89b990c21fafd27de633e2c5a9c7106b6d5b91901e3d53e + checksum: 10c0/ff9f6a78494463d2be82e5a0df73be0df488f61bc560126b07332720772b6717944df744fb95a4bc99d08816ada23e21a540743de38f52c6c8b9b7213d7336f1 languageName: node linkType: hard -"@kiltprotocol/jcs-data-integrity-proofs-common@npm:0.1.0-rc.3, @kiltprotocol/jcs-data-integrity-proofs-common@npm:^0.1.0-rc.3": - version: 0.1.0-rc.3 - resolution: "@kiltprotocol/jcs-data-integrity-proofs-common@npm:0.1.0-rc.3" +"@kiltprotocol/jcs-data-integrity-proofs-common@npm:0.1.0-rc.4, @kiltprotocol/jcs-data-integrity-proofs-common@npm:^0.1.0-rc.4": + version: 0.1.0-rc.4 + resolution: "@kiltprotocol/jcs-data-integrity-proofs-common@npm:0.1.0-rc.4" dependencies: "@noble/hashes": "npm:^1.3.0" canonicalize: "npm:^2.0.0" varint: "npm:^6.0.0" peerDependencies: "@scure/base": ^1.1.0 - checksum: 10c0/0006f1d429bf34b4a1619f344cd194556906d9988cebf6f6d28a730689ca4e7c24e0dcff25f5f03150210f6fcb97b87d014af7cd80a55200baecbc3abcd1491e + checksum: 10c0/fb9e2ab04bdd2d362ac33b1362231cdd74fb03d0f1a9700adc5bf60e938127906aede2290ce86c4681c53dd28e7e4951bbec82f9fc120c264e47d2f99f46d2f6 languageName: node linkType: hard @@ -2218,14 +2218,14 @@ __metadata: languageName: unknown linkType: soft -"@kiltprotocol/sr25519-jcs-2023@npm:0.1.0-rc.3": - version: 0.1.0-rc.3 - resolution: "@kiltprotocol/sr25519-jcs-2023@npm:0.1.0-rc.3" +"@kiltprotocol/sr25519-jcs-2023@npm:0.1.0-rc.4": + version: 0.1.0-rc.4 + resolution: "@kiltprotocol/sr25519-jcs-2023@npm:0.1.0-rc.4" dependencies: - "@kiltprotocol/jcs-data-integrity-proofs-common": "npm:^0.1.0-rc.3" - "@polkadot/util-crypto": "npm:^12.0.1" + "@kiltprotocol/jcs-data-integrity-proofs-common": "npm:^0.1.0-rc.4" + "@polkadot/util-crypto": "npm:^13.0.2" "@scure/base": "npm:^1.1.1" - checksum: 10c0/789d06939bed32e41f4543427f3eb32773fd39385ff1f09ff9df345f620c1fe67a1e249e1afef29f4b054e2a79388ff1fea59dd26136565d95f7bf7bda248c0f + checksum: 10c0/07a6749813f9e45d14c19c2bfa1af7be8dc1bcff82c7b2d3b6c7e9d67ed3862be69372e51b6d1cbabcdb2ac2906b07c14cf8b492fad944aa1dec7cba2e3b0635 languageName: node linkType: hard @@ -2254,9 +2254,9 @@ __metadata: version: 0.0.0-use.local resolution: "@kiltprotocol/utils@workspace:packages/utils" dependencies: - "@kiltprotocol/eddsa-jcs-2022": "npm:0.1.0-rc.3" - "@kiltprotocol/es256k-jcs-2023": "npm:0.1.0-rc.3" - "@kiltprotocol/sr25519-jcs-2023": "npm:0.1.0-rc.3" + "@kiltprotocol/eddsa-jcs-2022": "npm:0.1.0-rc.4" + "@kiltprotocol/es256k-jcs-2023": "npm:0.1.0-rc.4" + "@kiltprotocol/sr25519-jcs-2023": "npm:0.1.0-rc.4" "@kiltprotocol/types": "workspace:*" "@polkadot/api": "npm:^12.0.0" "@polkadot/keyring": "npm:^13.0.0" @@ -2502,17 +2502,6 @@ __metadata: languageName: node linkType: hard -"@polkadot/networks@npm:12.6.2": - version: 12.6.2 - resolution: "@polkadot/networks@npm:12.6.2" - dependencies: - "@polkadot/util": "npm:12.6.2" - "@substrate/ss58-registry": "npm:^1.44.0" - tslib: "npm:^2.6.2" - checksum: 10c0/44a482c46900058e6d5b25110cb5396382036057240cd4a8e0dae325fab54e689ec81bc43b047570581f14ce456b67310c05c1fe34c4b7f7d4e064f095f4c276 - languageName: node - linkType: hard - "@polkadot/networks@npm:13.0.2, @polkadot/networks@npm:^13.0.2": version: 13.0.2 resolution: "@polkadot/networks@npm:13.0.2" @@ -2698,41 +2687,6 @@ __metadata: languageName: node linkType: hard -"@polkadot/util-crypto@npm:^12.0.1": - version: 12.6.2 - resolution: "@polkadot/util-crypto@npm:12.6.2" - dependencies: - "@noble/curves": "npm:^1.3.0" - "@noble/hashes": "npm:^1.3.3" - "@polkadot/networks": "npm:12.6.2" - "@polkadot/util": "npm:12.6.2" - "@polkadot/wasm-crypto": "npm:^7.3.2" - "@polkadot/wasm-util": "npm:^7.3.2" - "@polkadot/x-bigint": "npm:12.6.2" - "@polkadot/x-randomvalues": "npm:12.6.2" - "@scure/base": "npm:^1.1.5" - tslib: "npm:^2.6.2" - peerDependencies: - "@polkadot/util": 12.6.2 - checksum: 10c0/b25f1574a2d4298c32b7a3cf3fa9f1b1237af3cc9e4ac16e75840097e9bcea11c8188abd5c46522d46d350edceb1e3e54fe8cbb01111e4eb643df4040ff41e2a - languageName: node - linkType: hard - -"@polkadot/util@npm:12.6.2": - version: 12.6.2 - resolution: "@polkadot/util@npm:12.6.2" - dependencies: - "@polkadot/x-bigint": "npm:12.6.2" - "@polkadot/x-global": "npm:12.6.2" - "@polkadot/x-textdecoder": "npm:12.6.2" - "@polkadot/x-textencoder": "npm:12.6.2" - "@types/bn.js": "npm:^5.1.5" - bn.js: "npm:^5.2.1" - tslib: "npm:^2.6.2" - checksum: 10c0/e426d31f8a6b8e8c57b86c18b419312906c5a169e5b2d89c15b54a5d6cf297912250d336f81926e07511ce825d36222d9e6387a01240aa6a20b11aa25dc8226a - languageName: node - linkType: hard - "@polkadot/util@npm:13.0.2, @polkadot/util@npm:^13.0.0, @polkadot/util@npm:^13.0.2": version: 13.0.2 resolution: "@polkadot/util@npm:13.0.2" @@ -2828,16 +2782,6 @@ __metadata: languageName: node linkType: hard -"@polkadot/x-bigint@npm:12.6.2": - version: 12.6.2 - resolution: "@polkadot/x-bigint@npm:12.6.2" - dependencies: - "@polkadot/x-global": "npm:12.6.2" - tslib: "npm:^2.6.2" - checksum: 10c0/78123efa2a5fad7fccb79dbe0c44f5506b70405a2b9b1dc9db9450ddd2f01791b011a46c9fff31ed8b21aace6f676179c4b7746c97ca254e8822bcf543e4d779 - languageName: node - linkType: hard - "@polkadot/x-bigint@npm:13.0.2, @polkadot/x-bigint@npm:^13.0.2": version: 13.0.2 resolution: "@polkadot/x-bigint@npm:13.0.2" @@ -2859,15 +2803,6 @@ __metadata: languageName: node linkType: hard -"@polkadot/x-global@npm:12.6.2": - version: 12.6.2 - resolution: "@polkadot/x-global@npm:12.6.2" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10c0/63738eb46465e3e43151d746321c178131385a734e1d3865fc76667fec9d4b1fb8b35a0d8ee75834035b54a4047e0bae86c4f2e465b16c73d4fc15ec4426446f - languageName: node - linkType: hard - "@polkadot/x-global@npm:13.0.2, @polkadot/x-global@npm:^13.0.2": version: 13.0.2 resolution: "@polkadot/x-global@npm:13.0.2" @@ -2877,19 +2812,6 @@ __metadata: languageName: node linkType: hard -"@polkadot/x-randomvalues@npm:12.6.2": - version: 12.6.2 - resolution: "@polkadot/x-randomvalues@npm:12.6.2" - dependencies: - "@polkadot/x-global": "npm:12.6.2" - tslib: "npm:^2.6.2" - peerDependencies: - "@polkadot/util": 12.6.2 - "@polkadot/wasm-util": "*" - checksum: 10c0/44920ec7a93ca0b5b0d2abae493fe5a9fb8cdb44b70029d431c1244a11dea0a9f14d216b4d14bde8b984199b9dd364a3ae68b51937784645343f686b3613c223 - languageName: node - linkType: hard - "@polkadot/x-randomvalues@npm:13.0.2": version: 13.0.2 resolution: "@polkadot/x-randomvalues@npm:13.0.2" @@ -2903,16 +2825,6 @@ __metadata: languageName: node linkType: hard -"@polkadot/x-textdecoder@npm:12.6.2": - version: 12.6.2 - resolution: "@polkadot/x-textdecoder@npm:12.6.2" - dependencies: - "@polkadot/x-global": "npm:12.6.2" - tslib: "npm:^2.6.2" - checksum: 10c0/d1aa46dc0c4f88bce3cb7aaadbede99c2fb159c0fd317fb9fe5b54bdbb83da9cce3a5d628e25892028b34cc4eeef72669c344f0af12e21f05429142cc7b4732d - languageName: node - linkType: hard - "@polkadot/x-textdecoder@npm:13.0.2": version: 13.0.2 resolution: "@polkadot/x-textdecoder@npm:13.0.2" @@ -2923,16 +2835,6 @@ __metadata: languageName: node linkType: hard -"@polkadot/x-textencoder@npm:12.6.2": - version: 12.6.2 - resolution: "@polkadot/x-textencoder@npm:12.6.2" - dependencies: - "@polkadot/x-global": "npm:12.6.2" - tslib: "npm:^2.6.2" - checksum: 10c0/fa234ce4d164991ea98f34e9eae2adf0c4d2b0806e2e30b11c41a52b432f8cbd91fb16945243809fd9433c513b8c7ab4c16d902b92faf7befaa523daae7459f4 - languageName: node - linkType: hard - "@polkadot/x-textencoder@npm:13.0.2": version: 13.0.2 resolution: "@polkadot/x-textencoder@npm:13.0.2" @@ -3029,7 +2931,7 @@ __metadata: languageName: node linkType: hard -"@substrate/ss58-registry@npm:^1.44.0, @substrate/ss58-registry@npm:^1.46.0": +"@substrate/ss58-registry@npm:^1.46.0": version: 1.49.0 resolution: "@substrate/ss58-registry@npm:1.49.0" checksum: 10c0/b50f32e2f4632b31b3e09cec026fef557b1b72f61b6811673f5b0fbe311c5394c2f19fc4c23f97b014c77eb2d0f535a8f079dfd3fb22d5a1d7b043ceeac0d9ac From d903907df676dc9193e413b0047009c0962c3421 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Thu, 18 Jul 2024 19:16:20 +0200 Subject: [PATCH 47/70] chore: set alpha.2 versions --- package.json | 2 +- packages/asset-credentials/package.json | 2 +- packages/chain-helpers/package.json | 2 +- packages/config/package.json | 2 +- packages/credentials/package.json | 2 +- packages/did/package.json | 2 +- packages/jsonld-suites/package.json | 2 +- packages/legacy-credentials/package.json | 2 +- packages/sdk-js/package.json | 2 +- packages/types/package.json | 2 +- packages/utils/package.json | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index cb9130291..1a2050d3d 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,6 @@ "typedoc": "^0.24.8", "typescript": "^4.8.3" }, - "version": "1.0.0-alpha.1", + "version": "1.0.0-alpha.2", "packageManager": "yarn@4.1.1" } diff --git a/packages/asset-credentials/package.json b/packages/asset-credentials/package.json index a16a02319..aaf8f47a9 100644 --- a/packages/asset-credentials/package.json +++ b/packages/asset-credentials/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/asset-credentials", - "version": "0.100.0-alpha.1", + "version": "0.100.0-alpha.2", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/chain-helpers/package.json b/packages/chain-helpers/package.json index f8d82da43..56c4f1df0 100644 --- a/packages/chain-helpers/package.json +++ b/packages/chain-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/chain-helpers", - "version": "0.100.0-alpha.1", + "version": "0.100.0-alpha.2", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/config/package.json b/packages/config/package.json index 11f44ef6a..aaa056fc6 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/config", - "version": "0.100.0-alpha.1", + "version": "0.100.0-alpha.2", "description": "", "type": "commonjs", "main": "./lib/index.js", diff --git a/packages/credentials/package.json b/packages/credentials/package.json index 242662d65..1d59ef877 100644 --- a/packages/credentials/package.json +++ b/packages/credentials/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/credentials", - "version": "0.100.0-alpha.1", + "version": "0.100.0-alpha.2", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/did/package.json b/packages/did/package.json index 930af0ebe..bffc9bed9 100644 --- a/packages/did/package.json +++ b/packages/did/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/did", - "version": "0.100.0-alpha.1", + "version": "0.100.0-alpha.2", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/jsonld-suites/package.json b/packages/jsonld-suites/package.json index d2ed2da78..3b6cdd939 100644 --- a/packages/jsonld-suites/package.json +++ b/packages/jsonld-suites/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/jsonld-suites", - "version": "0.100.0-alpha.1", + "version": "0.100.0-alpha.2", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/legacy-credentials/package.json b/packages/legacy-credentials/package.json index 64d9745d4..e03f1fe13 100644 --- a/packages/legacy-credentials/package.json +++ b/packages/legacy-credentials/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/legacy-credentials", - "version": "0.100.0-alpha.1", + "version": "0.100.0-alpha.2", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/sdk-js/package.json b/packages/sdk-js/package.json index 623971dcd..d0c553887 100644 --- a/packages/sdk-js/package.json +++ b/packages/sdk-js/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/sdk-js", - "version": "1.0.0-alpha.1", + "version": "1.0.0-alpha.2", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/types/package.json b/packages/types/package.json index c831b7b3a..71e4fe164 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/types", - "version": "0.100.0-alpha.1", + "version": "0.100.0-alpha.2", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", diff --git a/packages/utils/package.json b/packages/utils/package.json index ad0fcdd1c..fbb2cef66 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/utils", - "version": "0.100.0-alpha.1", + "version": "0.100.0-alpha.2", "description": "", "main": "./lib/cjs/index.js", "module": "./lib/esm/index.js", From 7b66646e680e3761bee0d269f5b799bae3e56460 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Fri, 19 Jul 2024 13:23:07 +0200 Subject: [PATCH 48/70] chore: use return in checkStatus --- packages/sdk-js/src/DidHelpers/checkResult.ts | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/checkResult.ts b/packages/sdk-js/src/DidHelpers/checkResult.ts index 5634ef764..7cb50bba0 100644 --- a/packages/sdk-js/src/DidHelpers/checkResult.ts +++ b/packages/sdk-js/src/DidHelpers/checkResult.ts @@ -47,50 +47,45 @@ function checkEventsForErrors( // Note: returns `undefined` in the case of `status === "inBlock`. function checkStatus(result: SubmittableResultValue): { - status: TransactionResult['status'] | undefined - error: Error | undefined - blockHash: HexString | undefined - blockNumber: BigInt | undefined + status?: TransactionResult['status'] + error?: Error + blockHash?: HexString + blockNumber?: BigInt } { - let status: TransactionResult['status'] | undefined - let blockHash - let blockNumber - let error + let blockNumber: BigInt | undefined switch (result.status.type) { case 'Finalized': case 'InBlock': // status must not be set here; this condition triggers a branch below // this is the block hash for both - status = undefined - blockHash = result.status.value.toHex() if ('blockNumber' in result) { blockNumber = (result.blockNumber as BlockNumber).toBigInt() } - break + return { + blockNumber, + blockHash: result.status.value.toHex(), + } case 'Dropped': case 'FinalityTimeout': case 'Invalid': case 'Usurped': - status = 'rejected' - error = new Error(result.status.type) - break + return { + status: 'rejected', + error: new Error(result.status.type), + } case 'Broadcast': case 'Future': case 'Ready': case 'Retracted': - status = 'unknown' - error = new Error(result.status.type) - break + return { + status: 'unknown', + error: new Error(result.status.type), + } default: - status = 'unknown' - error = new Error(`unknown tx status variant ${result.status.type}`) - } - - return { - status, - error, - blockHash, - blockNumber, + return { + status: 'unknown', + error: new Error(`unknown tx status variant ${result.status.type}`), + } } } From 3a9571a6809af9f75a0a61d87b580e87b90c6eec Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Fri, 19 Jul 2024 13:30:42 +0200 Subject: [PATCH 49/70] chore: move `mapError` and `assertStatus` --- packages/sdk-js/src/DidHelpers/checkResult.ts | 26 ++++++++++++++++--- packages/sdk-js/src/DidHelpers/common.ts | 19 -------------- packages/sdk-js/src/DidHelpers/interfaces.ts | 5 ---- 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/checkResult.ts b/packages/sdk-js/src/DidHelpers/checkResult.ts index 7cb50bba0..e65acd80c 100644 --- a/packages/sdk-js/src/DidHelpers/checkResult.ts +++ b/packages/sdk-js/src/DidHelpers/checkResult.ts @@ -12,11 +12,30 @@ import type { ApiPromise } from '@polkadot/api' import type { SubmittableResultValue } from '@polkadot/api/types' import type { BlockNumber } from '@polkadot/types/interfaces' import { u8aToHex, u8aToU8a } from '@polkadot/util' -import type { FrameSystemEventRecord as EventRecord } from '@kiltprotocol/augment-api' +import type { + FrameSystemEventRecord as EventRecord, + SpRuntimeDispatchError, +} from '@kiltprotocol/augment-api' import { resolver as DidResolver, signersForDid } from '@kiltprotocol/did' import type { Did, HexString } from '@kiltprotocol/types' import type { SharedArguments, TransactionResult } from './interfaces.js' -import { assertStatus, mapError } from './common.js' + +function mapError(err: SpRuntimeDispatchError, api: ApiPromise): Error { + if (err.isModule) { + const { docs, method, section } = api.registry.findMetaError(err.asModule) + return new Error(`${section}.${method}: ${docs}`) + } + 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}'`) + } +} function checkEventsForErrors( api: ApiPromise, @@ -56,12 +75,11 @@ function checkStatus(result: SubmittableResultValue): { switch (result.status.type) { case 'Finalized': case 'InBlock': - // status must not be set here; this condition triggers a branch below - // this is the block hash for both if ('blockNumber' in result) { blockNumber = (result.blockNumber as BlockNumber).toBigInt() } return { + status: undefined, blockNumber, blockHash: result.status.value.toHex(), } diff --git a/packages/sdk-js/src/DidHelpers/common.ts b/packages/sdk-js/src/DidHelpers/common.ts index 6d413f9be..bcf2ceced 100644 --- a/packages/sdk-js/src/DidHelpers/common.ts +++ b/packages/sdk-js/src/DidHelpers/common.ts @@ -8,8 +8,6 @@ /* eslint-disable jsdoc/require-jsdoc */ // functions in this file are not meant to be public -import type { ApiPromise } from '@polkadot/api' -import type { SpRuntimeDispatchError } from '@kiltprotocol/augment-api' import { Blockchain } from '@kiltprotocol/chain-helpers' import { multibaseKeyToDidKey } from '@kiltprotocol/did' @@ -19,23 +17,6 @@ import type { TransactionHandlers, } from './interfaces.js' -export function mapError(err: SpRuntimeDispatchError, api: ApiPromise): Error { - if (err.isModule) { - const { docs, method, section } = api.registry.findMetaError(err.asModule) - return new Error(`${section}.${method}: ${docs}`) - } - return new Error(`${err.type}: ${err.value.toHuman()}`) -} - -export 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 submitImpl( getSubmittable: TransactionHandlers['getSubmittable'], options: Pick & { diff --git a/packages/sdk-js/src/DidHelpers/interfaces.ts b/packages/sdk-js/src/DidHelpers/interfaces.ts index 43cf3fb78..4c91ad866 100644 --- a/packages/sdk-js/src/DidHelpers/interfaces.ts +++ b/packages/sdk-js/src/DidHelpers/interfaces.ts @@ -45,11 +45,6 @@ export interface TransactionResult { error: Error txHash: HexString } - // we may or may not add these, given that you can also disambiguate based on the status - // isConfirmed: boolean - // isFailed: boolean - // isRejected: boolean - // isUnknown: boolean } export interface TransactionHandlers { From ab3d2eef297223b8089ec7fc1e10a11504539d3f Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Fri, 19 Jul 2024 13:58:05 +0200 Subject: [PATCH 50/70] chore: improve performance of `checkEventsForErrors` --- packages/sdk-js/src/DidHelpers/checkResult.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/checkResult.ts b/packages/sdk-js/src/DidHelpers/checkResult.ts index e65acd80c..889718459 100644 --- a/packages/sdk-js/src/DidHelpers/checkResult.ts +++ b/packages/sdk-js/src/DidHelpers/checkResult.ts @@ -41,8 +41,8 @@ function checkEventsForErrors( api: ApiPromise, txEvents: EventRecord[] = [] ): Error | undefined { - let error - txEvents.forEach(({ event }) => { + let error: Error | undefined + txEvents.some(({ event }) => { if (api.events.system.ExtrinsicFailed.is(event)) { error = mapError(event.data[0], api) } else if (api.events.proxy.ProxyExecuted.is(event)) { @@ -60,6 +60,10 @@ function checkEventsForErrors( } else if (api.events.utility.BatchInterrupted.is(event)) { error = mapError(event.data[1], api) } + if (typeof error !== 'undefined') { + return true + } + return false }) return error } @@ -158,7 +162,7 @@ export async function checkResultImpl( // Case where `SubmittableResultValue` is provided. if ('status' in result) { txEvents = result.events ?? [] - ;({ status, blockHash, blockNumber, error } = checkStatus(result)) + ; ({ status, blockHash, blockNumber, error } = checkStatus(result)) } // Case where block hash and tx hash are provided. else if ( @@ -168,7 +172,7 @@ export async function checkResultImpl( typeof result.txHash === 'string' ) { // Set blockNumber, blockHash, and the transactionEvents. - ;({ blockNumber, blockHash, txEvents } = await resolveBlockAndEvents( + ; ({ blockNumber, blockHash, txEvents } = await resolveBlockAndEvents( result, api )) From 5fa8bedcd9c43dbdfd6a3026170807bb2b73dcf0 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Fri, 19 Jul 2024 14:13:12 +0200 Subject: [PATCH 51/70] chore: improve if statement --- packages/sdk-js/src/DidHelpers/checkResult.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/checkResult.ts b/packages/sdk-js/src/DidHelpers/checkResult.ts index 889718459..c71e8d5fd 100644 --- a/packages/sdk-js/src/DidHelpers/checkResult.ts +++ b/packages/sdk-js/src/DidHelpers/checkResult.ts @@ -162,7 +162,7 @@ export async function checkResultImpl( // Case where `SubmittableResultValue` is provided. if ('status' in result) { txEvents = result.events ?? [] - ; ({ status, blockHash, blockNumber, error } = checkStatus(result)) + ;({ status, blockHash, blockNumber, error } = checkStatus(result)) } // Case where block hash and tx hash are provided. else if ( @@ -172,7 +172,7 @@ export async function checkResultImpl( typeof result.txHash === 'string' ) { // Set blockNumber, blockHash, and the transactionEvents. - ; ({ blockNumber, blockHash, txEvents } = await resolveBlockAndEvents( + ;({ blockNumber, blockHash, txEvents } = await resolveBlockAndEvents( result, api )) @@ -190,10 +190,9 @@ export async function checkResultImpl( } const eventMatch = checkIfEventsMatch(expectedEvents, txEvents) - const isSuccess = !error && eventMatch - status = isSuccess ? 'confirmed' : 'failed' - if (!isSuccess && !error) { + status = !error && eventMatch ? 'confirmed' : 'failed' + if (!error && !eventMatch) { error = new Error('did not find expected events') } } From 385f66a4ad898dc8ad1d366ae575dcae8b7b72a2 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Mon, 22 Jul 2024 09:43:54 +0200 Subject: [PATCH 52/70] fix: transaction result serialization --- packages/sdk-js/src/DidHelpers/checkResult.ts | 23 +++++++++++--- packages/sdk-js/src/DidHelpers/interfaces.ts | 2 ++ .../sdk-js/src/DidHelpers/transact.spec.ts | 31 ++++++++++++------- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/checkResult.ts b/packages/sdk-js/src/DidHelpers/checkResult.ts index c71e8d5fd..b9afede57 100644 --- a/packages/sdk-js/src/DidHelpers/checkResult.ts +++ b/packages/sdk-js/src/DidHelpers/checkResult.ts @@ -112,21 +112,23 @@ function checkStatus(result: SubmittableResultValue): { } async function resolveBlockAndEvents( - result: { blockHash: HexString; txHash: HexString }, + blockInfo: { blockHash: HexString; txHash: HexString }, api: ApiPromise ): Promise<{ blockNumber: BigInt blockHash: HexString txEvents: EventRecord[] }> { - const txHashHash = api.createType('Hash', result.blockHash) + const txHashHash = api.createType('Hash', blockInfo.blockHash) const { block: { block }, events, } = await api.derive.tx.events(txHashHash) const blockNumber = block.header.number.toBigInt() - const { blockHash } = result - const txIndex = block.extrinsics.findIndex((tx) => tx.hash.eq(result.txHash)) + const { blockHash } = blockInfo + const txIndex = block.extrinsics.findIndex((tx) => + tx.hash.eq(blockInfo.txHash) + ) const txEvents = events.filter( ({ phase }) => phase.isApplyExtrinsic && phase.asApplyExtrinsic.eqn(txIndex) ) @@ -223,6 +225,11 @@ export async function checkResultImpl( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion block: { hash: blockHash!, number: blockNumber! }, events: txEvents.map(({ event }) => event), + toJSON() { + const clone = { ...this } as any + clone.block.number = clone.block.number.toString() + return clone + }, } }, get asUnknown(): TransactionResult['asUnknown'] { @@ -251,6 +258,14 @@ export async function checkResultImpl( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion block: { hash: blockHash!, number: blockNumber! }, events: txEvents.map(({ event }) => event), + toJSON() { + const clone = { ...this } as any + clone.block = { + number: clone.block.number.toString(), + hash: this.block.hash, + } + return clone + }, } }, } diff --git a/packages/sdk-js/src/DidHelpers/interfaces.ts b/packages/sdk-js/src/DidHelpers/interfaces.ts index 4c91ad866..0540eab6f 100644 --- a/packages/sdk-js/src/DidHelpers/interfaces.ts +++ b/packages/sdk-js/src/DidHelpers/interfaces.ts @@ -26,6 +26,7 @@ export interface TransactionResult { didDocument: DidDocument block: { hash: HexString; number: BigInt } events: GenericEvent[] + toJSON: () => any } asFailed: { error: Error @@ -34,6 +35,7 @@ export interface TransactionResult { didDocument?: DidDocument block: { hash: HexString; number: BigInt } events: GenericEvent[] + toJSON: () => any } asRejected: { error: Error diff --git a/packages/sdk-js/src/DidHelpers/transact.spec.ts b/packages/sdk-js/src/DidHelpers/transact.spec.ts index 378eb1f27..92099a7e5 100644 --- a/packages/sdk-js/src/DidHelpers/transact.spec.ts +++ b/packages/sdk-js/src/DidHelpers/transact.spec.ts @@ -80,18 +80,17 @@ describe('transact', () => { expect(parsed.method).toHaveProperty('section', 'did') expect(parsed.method).toHaveProperty('method', 'submitDidCall') - await expect( - checkResult( - new SubmittableResult({ - blockNumber: mockedApi.createType('BlockNumber', 1000), - status: mockedApi.createType('ExtrinsicStatus', { - inBlock: new Uint8Array(32).fill(2), - }), - txHash: parsed.hash, - events: makeAttestationCreatedEvents([[]]), - }) - ) - ).resolves.toMatchObject>({ + const result = await checkResult( + new SubmittableResult({ + blockNumber: mockedApi.createType('BlockNumber', 1000), + status: mockedApi.createType('ExtrinsicStatus', { + inBlock: new Uint8Array(32).fill(2), + }), + txHash: parsed.hash, + events: makeAttestationCreatedEvents([[]]), + }) + ) + expect(result).toMatchObject>({ status: 'confirmed', asConfirmed: expect.objectContaining({ txHash: parsed.hash.toHex(), @@ -103,5 +102,13 @@ describe('transact', () => { }, }), }) + // TODO move serialzation test to `checkResult.spec.ts` once created and + // test the `asFailed` case. + const confirmed = result.asConfirmed + expect(typeof confirmed.block.number).toBe('bigint') + const resultStringified = JSON.stringify(confirmed) + const resultRebuiltObj = JSON.parse(resultStringified) + expect(BigInt(resultRebuiltObj.block.number)).toBe(BigInt(1000)) + expect(typeof confirmed.block.number).toBe('bigint') }) }) From f1eea0a5112aad300de8c3a71bd11e5678d381dd Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Mon, 22 Jul 2024 09:52:10 +0200 Subject: [PATCH 53/70] fix: asFailed --- packages/sdk-js/src/DidHelpers/checkResult.ts | 5 ++++- packages/sdk-js/src/DidHelpers/transact.spec.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/checkResult.ts b/packages/sdk-js/src/DidHelpers/checkResult.ts index b9afede57..93b959666 100644 --- a/packages/sdk-js/src/DidHelpers/checkResult.ts +++ b/packages/sdk-js/src/DidHelpers/checkResult.ts @@ -227,7 +227,10 @@ export async function checkResultImpl( events: txEvents.map(({ event }) => event), toJSON() { const clone = { ...this } as any - clone.block.number = clone.block.number.toString() + clone.block = { + number: clone.block.number.toString(), + hash: this.block.hash, + } return clone }, } diff --git a/packages/sdk-js/src/DidHelpers/transact.spec.ts b/packages/sdk-js/src/DidHelpers/transact.spec.ts index 92099a7e5..373e20c6b 100644 --- a/packages/sdk-js/src/DidHelpers/transact.spec.ts +++ b/packages/sdk-js/src/DidHelpers/transact.spec.ts @@ -102,7 +102,7 @@ describe('transact', () => { }, }), }) - // TODO move serialzation test to `checkResult.spec.ts` once created and + // TODO move serialzation test to `checkResult.spec.ts` once created and // test the `asFailed` case. const confirmed = result.asConfirmed expect(typeof confirmed.block.number).toBe('bigint') From 52e5f37c4f4b94f595655fa071e29879f5d5ab26 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Thu, 18 Jul 2024 13:15:24 +0200 Subject: [PATCH 54/70] feat: generateKeypair helper --- packages/sdk-js/src/index.ts | 3 ++- packages/utils/src/Signers.ts | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/packages/sdk-js/src/index.ts b/packages/sdk-js/src/index.ts index cef20b3a2..c1bd06620 100644 --- a/packages/sdk-js/src/index.ts +++ b/packages/sdk-js/src/index.ts @@ -22,7 +22,7 @@ import { resolver as DidResolver } from '@kiltprotocol/did' import * as DidHelpers from './DidHelpers/index.js' const { signAndSubmitTx } = Blockchain // TODO: maybe we don't even need that if we have the identity class -const { signerFromKeypair } = Signers +const { signerFromKeypair, generateKeypair } = Signers export { init, @@ -33,6 +33,7 @@ export { Verifier, Issuer, signerFromKeypair, + generateKeypair, signAndSubmitTx, ConfigService, DidHelpers, diff --git a/packages/utils/src/Signers.ts b/packages/utils/src/Signers.ts index e62400562..5f00a6ffa 100644 --- a/packages/utils/src/Signers.ts +++ b/packages/utils/src/Signers.ts @@ -17,6 +17,7 @@ import { import { blake2AsU8a, encodeAddress, + randomAsHex, secp256k1Sign, } from '@polkadot/util-crypto' import type { Keypair } from '@polkadot/util-crypto/types' @@ -29,13 +30,11 @@ import { createSigner as es256kSigner, cryptosuite as es256kSuite, } from '@kiltprotocol/es256k-jcs-2023' -// import { decodeMultikeyVerificationMethod } from '@kiltprotocol/jcs-data-integrity-proofs-common' import { createSigner as sr25519Signer, cryptosuite as sr25519Suite, } from '@kiltprotocol/sr25519-jcs-2023' -// import { multibaseKeyToDidKey } from '@kiltprotocol/did' import type { DidDocument, DidUrl, @@ -43,7 +42,7 @@ import type { SignerInterface, UriFragment, } from '@kiltprotocol/types' - +import { makeKeypairFromUri } from './Crypto.js' import { DidError, NoSuitableSignerError } from './SDKErrors.js' export const ALGORITHMS = Object.freeze({ @@ -469,3 +468,33 @@ export function getPolkadotSigner( }, } } + +export function generateKeypair(args?: { + seed?: string + type?: T +}): Keypair & { type: T } +export function generateKeypair({ + seed = randomAsHex(32), + type = 'ed25519', +}: { + seed?: string + type?: string +} = {}): Keypair & { type: string } { + let typeForKeyring = type as KeyringPair['type'] + switch (type.toLowerCase()) { + case 'secp256k1': + typeForKeyring = 'ecdsa' + break + case 'x25519': + typeForKeyring = 'ed25519' + break + default: + } + + const keyRingPair = makeKeypairFromUri( + seed.toLowerCase(), + typeForKeyring as any + ) + const { secretKey, publicKey } = extractPk(keyRingPair) + return { secretKey, publicKey, type } +} From 035db971fc439bdfeeedceb0dec15c72280d09d2 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Thu, 18 Jul 2024 15:05:46 +0200 Subject: [PATCH 55/70] refactor!: switch to using multikey encoding --- packages/did/package.json | 4 +- packages/did/src/Did.chain.ts | 32 ++-- packages/did/src/Did.signature.spec.ts | 2 +- packages/did/src/Did.signature.ts | 11 +- packages/did/src/Did.utils.ts | 114 ++---------- .../did/src/DidDetails/LightDidDetails.ts | 26 ++- packages/sdk-js/src/DidHelpers/interfaces.ts | 22 +-- packages/sdk-js/src/index.ts | 4 +- packages/types/src/Signers.ts | 8 + packages/utils/package.json | 4 +- packages/utils/src/Multikey.ts | 162 ++++++++++++++++++ packages/utils/src/Signers.ts | 36 ++-- packages/utils/src/index.ts | 1 + yarn.lock | 4 +- 14 files changed, 257 insertions(+), 173 deletions(-) create mode 100644 packages/utils/src/Multikey.ts diff --git a/packages/did/package.json b/packages/did/package.json index bffc9bed9..2437a65e6 100644 --- a/packages/did/package.json +++ b/packages/did/package.json @@ -40,15 +40,13 @@ "@digitalbazaar/multikey-context": "^2.0.1", "@digitalbazaar/security-context": "^1.0.1", "@kiltprotocol/config": "workspace:*", - "@kiltprotocol/jcs-data-integrity-proofs-common": "0.1.0-rc.4", "@kiltprotocol/types": "workspace:*", "@kiltprotocol/utils": "workspace:*", "@polkadot/api": "^12.0.0", "@polkadot/keyring": "^13.0.0", "@polkadot/types": "^12.0.0", "@polkadot/util": "^13.0.0", - "@polkadot/util-crypto": "^13.0.0", - "varint": "^6.0.0" + "@polkadot/util-crypto": "^13.0.0" }, "peerDependenciesMeta": { "@kiltprotocol/augment-api": { diff --git a/packages/did/src/Did.chain.ts b/packages/did/src/Did.chain.ts index 079023915..967b58ebb 100644 --- a/packages/did/src/Did.chain.ts +++ b/packages/did/src/Did.chain.ts @@ -8,6 +8,7 @@ import type { Option } from '@polkadot/types' import type { AccountId32, Extrinsic, Hash } from '@polkadot/types/interfaces' import type { AnyNumber } from '@polkadot/types/types' + import type { DidDidDetails, DidDidDetailsDidAuthorizedCallOperation, @@ -15,6 +16,7 @@ import type { DidServiceEndpointsDidEndpoint, KiltSupportDeposit, } from '@kiltprotocol/augment-api' +import { ConfigService } from '@kiltprotocol/config' import type { BN, Deposit, @@ -26,23 +28,27 @@ import type { SubmittableExtrinsic, UriFragment, } from '@kiltprotocol/types' -import { ConfigService } from '@kiltprotocol/config' -import { Crypto, SDKErrors, Signers, ss58Format } from '@kiltprotocol/utils' +import { + Crypto, + Multikey, + SDKErrors, + Signers, + ss58Format, +} from '@kiltprotocol/utils' +import { + getAddressFromVerificationMethod, + getFullDid, + parse, +} from './Did.utils.js' import type { DidEncryptionMethodType, - NewService, DidSigningMethodType, - NewDidVerificationKey, NewDidEncryptionKey, + NewDidVerificationKey, + NewService, } from './DidDetails/DidDetails.js' import { isValidVerificationMethodType } from './DidDetails/DidDetails.js' -import { - keypairToMultibaseKey, - getAddressFromVerificationMethod, - getFullDid, - parse, -} from './Did.utils.js' import { type NewLightDidVerificationKey, createLightDidDocument, @@ -395,9 +401,9 @@ export async function getStoreTx( } const [authenticationKey] = authentication - const did = getAddressFromVerificationMethod({ - publicKeyMultibase: keypairToMultibaseKey(authenticationKey), - }) + const did = getAddressFromVerificationMethod( + Multikey.encodeMultibaseKeypair(authenticationKey) + ) const newAttestationKey = assertionMethod && diff --git a/packages/did/src/Did.signature.spec.ts b/packages/did/src/Did.signature.spec.ts index 79fc31936..506644a5e 100644 --- a/packages/did/src/Did.signature.spec.ts +++ b/packages/did/src/Did.signature.spec.ts @@ -292,7 +292,7 @@ describe('full DID', () => { { controller: `did:kilt:${keypair.address}`, id: `did:kilt:${keypair.address}#0x12345`, - publicKeyMultibase: keypairToMultibaseKey(keypair), + publicKeyMultibase: keypairToMultibaseKey(keypair).publicKeyMultibase, type: 'Multikey', }, ], diff --git a/packages/did/src/Did.signature.ts b/packages/did/src/Did.signature.ts index ac57fa130..4339a32db 100644 --- a/packages/did/src/Did.signature.ts +++ b/packages/did/src/Did.signature.ts @@ -14,6 +14,7 @@ import type { DidSignature, DidUrl, KeyringPair, + MultibaseKeyPair, SignatureVerificationRelationship, SignerInterface, } from '@kiltprotocol/types' @@ -190,15 +191,7 @@ export function signatureFromJson(input: DidSignature | LegacyDidSignature): { */ export async function signersForDid( didDocument: DidDocument, - ...keypairs: Array< - | SignerInterface - | KeyringPair - | Keypair - | { - secretKeyMultibase: `z${string}` - publicKeyMultibase: `z${string}` - } - > + ...keypairs: Array ): Promise>> { const didKeys = didDocument.verificationMethod?.map( ({ publicKeyMultibase, id }) => ({ diff --git a/packages/did/src/Did.utils.ts b/packages/did/src/Did.utils.ts index 0e46e0726..e4a11a68a 100644 --- a/packages/did/src/Did.utils.ts +++ b/packages/did/src/Did.utils.ts @@ -5,21 +5,15 @@ * found in the LICENSE file in the root directory of this source tree. */ -import { blake2AsU8a, encodeAddress, base58Encode } from '@polkadot/util-crypto' -import { u8aConcat } from '@polkadot/util' import type { - Base58BtcMultibaseString, Did, DidUrl, - KeyringPair, KiltAddress, UriFragment, VerificationMethod, } from '@kiltprotocol/types' -import { DataUtils, SDKErrors, ss58Format } from '@kiltprotocol/utils' -// @ts-expect-error Not a typescript module -import * as varint from 'varint' -import { decodeBase58BtcMultikey } from '@kiltprotocol/jcs-data-integrity-proofs-common' +import { DataUtils, Multikey, SDKErrors, ss58Format } from '@kiltprotocol/utils' +import { blake2AsU8a, encodeAddress } from '@polkadot/util-crypto' import type { DidVerificationMethodType } from './DidDetails/DidDetails.js' import { parseDocumentFromLightDid } from './DidDetails/LightDidDetails.js' @@ -133,25 +127,6 @@ type DecodedVerificationMethod = { keyType: DidVerificationMethodType } -const MULTICODEC_ECDSA_PREFIX = 0xe7 -const MULTICODEC_X25519_PREFIX = 0xec -const MULTICODEC_ED25519_PREFIX = 0xed -const MULTICODEC_SR25519_PREFIX = 0xef - -const multicodecPrefixes: Record = - { - [MULTICODEC_ECDSA_PREFIX]: ['ecdsa', 33], - [MULTICODEC_X25519_PREFIX]: ['x25519', 32], - [MULTICODEC_ED25519_PREFIX]: ['ed25519', 32], - [MULTICODEC_SR25519_PREFIX]: ['sr25519', 32], - } -const multicodecReversePrefixes: Record = { - ecdsa: MULTICODEC_ECDSA_PREFIX, - x25519: MULTICODEC_X25519_PREFIX, - ed25519: MULTICODEC_ED25519_PREFIX, - sr25519: MULTICODEC_SR25519_PREFIX, -} - /** * Decode a Multikey representation of a verification method into its fundamental components: the public key and the key type. * @@ -161,68 +136,19 @@ const multicodecReversePrefixes: Record = { export function multibaseKeyToDidKey( publicKeyMultibase: VerificationMethod['publicKeyMultibase'] ): DecodedVerificationMethod { - const { keyBytes, prefix } = decodeBase58BtcMultikey(publicKeyMultibase) - - const [keyType, expectedPublicKeyLength] = multicodecPrefixes[prefix] - if (keyType === undefined) { - throw new SDKErrors.DidError( - `Cannot decode key type for multibase key "${publicKeyMultibase}".` - ) - } - if (keyBytes.length !== expectedPublicKeyLength) { + const { publicKey, type } = Multikey.decodeMultibaseKeypair({ + publicKeyMultibase, + }) + const expectedPublicKeyLength = type === 'secp256k1' ? 33 : 32 + if (publicKey.length !== expectedPublicKeyLength) { throw new SDKErrors.DidError( - `Key of type "${keyType}" is expected to be ${expectedPublicKeyLength} bytes long. Provided key is ${keyBytes.length} bytes long instead.` + `Key of type "${type}" is expected to be ${expectedPublicKeyLength} bytes long. Provided key is ${publicKey.length} bytes long instead.` ) } return { - keyType, - publicKey: keyBytes, - } -} - -// TODO: This could also be exposed in a new release candidate of the `@kiltprotocol/jcs-data-integrity-proofs-common` package. -function multibase58BtcKeyBytesEncoding( - key: Uint8Array, - keyPrefix: number -): Base58BtcMultibaseString { - const varintEncodedPrefix = varint.encode(keyPrefix) - const prefixedKey = u8aConcat(varintEncodedPrefix, key) - const base58BtcEncodedKey = base58Encode(prefixedKey) - return `z${base58BtcEncodedKey}` -} - -/** - * Calculate the Multikey representation of a keypair given its type and public key. - * - * @param keypair The input keypair to encode as Multikey. - * @param keypair.type The keypair {@link DidVerificationMethodType}. - * @param keypair.publicKey The keypair public key. - * @returns The Multikey representation (i.e., multicodec-prefixed, then multibase encoded) of the provided keypair. - */ -export function keypairToMultibaseKey({ - type, - publicKey, -}: Pick & { - type: DidVerificationMethodType -}): VerificationMethod['publicKeyMultibase'] { - const multiCodecPublicKeyPrefix = multicodecReversePrefixes[type] - if (multiCodecPublicKeyPrefix === undefined) { - throw new SDKErrors.DidError( - `The provided key type "${type}" is not supported.` - ) - } - const expectedPublicKeySize = multicodecPrefixes[multiCodecPublicKeyPrefix][1] - if (publicKey.length !== expectedPublicKeySize) { - throw new SDKErrors.DidError( - `Key of type "${type}" is expected to be ${expectedPublicKeySize} bytes long. Provided key is ${publicKey.length} bytes long instead.` - ) - } - const prefixedEncodedPublicKey = multibase58BtcKeyBytesEncoding( + keyType: type === 'secp256k1' ? 'ecdsa' : type, publicKey, - multiCodecPublicKeyPrefix - ) - - return prefixedEncodedPublicKey + } } /** @@ -240,28 +166,16 @@ export function didKeyToVerificationMethod( id: IdType, { keyType, publicKey }: DecodedVerificationMethod ): VerificationMethod { - const multiCodecPublicKeyPrefix = multicodecReversePrefixes[keyType] - if (multiCodecPublicKeyPrefix === undefined) { - throw new SDKErrors.DidError( - `Provided key type "${keyType}" not supported.` - ) - } - const expectedPublicKeySize = multicodecPrefixes[multiCodecPublicKeyPrefix][1] - if (publicKey.length !== expectedPublicKeySize) { - throw new SDKErrors.DidError( - `Key of type "${keyType}" is expected to be ${expectedPublicKeySize} bytes long. Provided key is ${publicKey.length} bytes long instead.` - ) - } - const prefixedEncodedPublicKey = multibase58BtcKeyBytesEncoding( + const { publicKeyMultibase } = Multikey.encodeMultibaseKeypair({ publicKey, - multiCodecPublicKeyPrefix - ) + type: keyType, + }) return { controller, id, type: 'Multikey', - publicKeyMultibase: prefixedEncodedPublicKey, + publicKeyMultibase, } } diff --git a/packages/did/src/DidDetails/LightDidDetails.ts b/packages/did/src/DidDetails/LightDidDetails.ts index f92d68044..d08705569 100644 --- a/packages/did/src/DidDetails/LightDidDetails.ts +++ b/packages/did/src/DidDetails/LightDidDetails.ts @@ -5,29 +5,27 @@ * found in the LICENSE file in the root directory of this source tree. */ -import type { DidDocument, Did } from '@kiltprotocol/types' - import { base58Decode, base58Encode, decodeAddress, } from '@polkadot/util-crypto' -import { cbor, SDKErrors, ss58Format } from '@kiltprotocol/utils' -import type { - NewDidEncryptionKey, - NewDidVerificationKey, - NewService, - DidSigningMethodType, -} from './DidDetails.js' +import type { Did, DidDocument } from '@kiltprotocol/types' +import { cbor, Multikey, SDKErrors, ss58Format } from '@kiltprotocol/utils' +import { urlFragmentToChain, validateNewService } from '../Did.chain.js' import { - keypairToMultibaseKey, didKeyToVerificationMethod, getAddressFromVerificationMethod, parse, } from '../Did.utils.js' -import { urlFragmentToChain, validateNewService } from '../Did.chain.js' +import type { + DidSigningMethodType, + NewDidEncryptionKey, + NewDidVerificationKey, + NewService, +} from './DidDetails.js' import { addKeypairAsVerificationMethod, encryptionMethodTypes, @@ -231,9 +229,9 @@ export function createLightDidDocument({ // Validity is checked in validateCreateDocumentInput const authenticationKeyTypeEncoding = verificationKeyTypeToLightDidEncoding[authentication[0].type] - const address = getAddressFromVerificationMethod({ - publicKeyMultibase: keypairToMultibaseKey(authentication[0]), - }) + const address = getAddressFromVerificationMethod( + Multikey.encodeMultibaseKeypair(authentication[0]) + ) const encodedDetailsString = encodedDetails ? `:${encodedDetails}` : '' const did = diff --git a/packages/sdk-js/src/DidHelpers/interfaces.ts b/packages/sdk-js/src/DidHelpers/interfaces.ts index 4c91ad866..ced84e9b2 100644 --- a/packages/sdk-js/src/DidHelpers/interfaces.ts +++ b/packages/sdk-js/src/DidHelpers/interfaces.ts @@ -14,8 +14,11 @@ import type { DidDocument, HexString, KeyringPair, + MultibaseKeyPair, + MultibasePublicKey, SignerInterface, } from '@kiltprotocol/types' +import type { Multikey } from '@kiltprotocol/utils' export interface TransactionResult { status: 'confirmed' | 'failed' | 'rejected' | 'unknown' @@ -89,18 +92,7 @@ export interface TransactionHandlers { }> } -/** Base58 encoded bytes, using the bitcoin alphabet. */ -type Base58Btc = string -/** Multibase encoding of a public- or private key including multicodec variant flag. */ -export type KeyMultibaseEncoded = `z${Base58Btc}` - -export type AcceptedSigners = - | SignerInterface - | KeyringPair - | { - secretKeyMultibase: KeyMultibaseEncoded - publicKeyMultibase: KeyMultibaseEncoded - } +export type AcceptedSigners = SignerInterface | KeyringPair | MultibaseKeyPair export type SharedArguments = { didDocument: DidDocument @@ -111,10 +103,10 @@ export type SharedArguments = { type PublicKeyAndType = { publicKey: Uint8Array - type: KeyringPair['type'] | 'x25519' + type: Multikey.KnownTypeString } export type AcceptedPublicKeyEncodings = - | KeyMultibaseEncoded - | { publicKeyMultibase: KeyMultibaseEncoded } + | MultibasePublicKey['publicKeyMultibase'] + | MultibasePublicKey | PublicKeyAndType // interface allows KeyringPair too diff --git a/packages/sdk-js/src/index.ts b/packages/sdk-js/src/index.ts index c1bd06620..485126fb4 100644 --- a/packages/sdk-js/src/index.ts +++ b/packages/sdk-js/src/index.ts @@ -22,7 +22,7 @@ import { resolver as DidResolver } from '@kiltprotocol/did' import * as DidHelpers from './DidHelpers/index.js' const { signAndSubmitTx } = Blockchain // TODO: maybe we don't even need that if we have the identity class -const { signerFromKeypair, generateKeypair } = Signers +const { getSignersForKeypair, generateKeypair } = Signers export { init, @@ -32,7 +32,7 @@ export { Holder, Verifier, Issuer, - signerFromKeypair, + getSignersForKeypair, generateKeypair, signAndSubmitTx, ConfigService, diff --git a/packages/types/src/Signers.ts b/packages/types/src/Signers.ts index cbfe18add..66fc06ef6 100644 --- a/packages/types/src/Signers.ts +++ b/packages/types/src/Signers.ts @@ -5,6 +5,8 @@ * found in the LICENSE file in the root directory of this source tree. */ +import type { Base58BtcMultibaseString, VerificationMethod } from './Did.js' + export type SignerInterface< Alg extends string = string, Id extends string = string @@ -13,3 +15,9 @@ export type SignerInterface< id: Id sign: (input: { data: Uint8Array }) => Promise } + +export type MultibasePublicKey = Pick +export type MultibaseSecretKey = { + secretKeyMultibase: Base58BtcMultibaseString +} +export type MultibaseKeyPair = MultibasePublicKey & MultibaseSecretKey diff --git a/packages/utils/package.json b/packages/utils/package.json index fbb2cef66..c14e81f5e 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -37,6 +37,7 @@ "dependencies": { "@kiltprotocol/eddsa-jcs-2022": "0.1.0-rc.4", "@kiltprotocol/es256k-jcs-2023": "0.1.0-rc.4", + "@kiltprotocol/jcs-data-integrity-proofs-common": "0.1.0-rc.4", "@kiltprotocol/sr25519-jcs-2023": "0.1.0-rc.4", "@kiltprotocol/types": "workspace:*", "@polkadot/api": "^12.0.0", @@ -45,6 +46,7 @@ "@polkadot/util-crypto": "^13.0.0", "cbor-web": "^9.0.0", "tweetnacl": "^1.0.3", - "uuid": "^10.0.0" + "uuid": "^10.0.0", + "varint": "^6.0.0" } } diff --git a/packages/utils/src/Multikey.ts b/packages/utils/src/Multikey.ts new file mode 100644 index 000000000..d1b0d82b4 --- /dev/null +++ b/packages/utils/src/Multikey.ts @@ -0,0 +1,162 @@ +/** + * 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 { decodeBase58BtcMultikey } from '@kiltprotocol/jcs-data-integrity-proofs-common' +// @ts-expect-error Not a typescript module +import * as varint from 'varint' + +import { + Base58BtcMultibaseString, + KeyringPair, + MultibaseKeyPair, + MultibasePublicKey, + MultibaseSecretKey, +} from '@kiltprotocol/types' +import { u8aConcat } from '@polkadot/util' +import { base58Encode } from '@polkadot/util-crypto' +import { DidError } from './SDKErrors.js' + +const MULTICODEC_SECP256K1_PREFIXES = [0xe7, 0x1301] as const +const MULTICODEC_X25519_PREFIXES = [0xec, 0x1302] as const +const MULTICODEC_ED25519_PREFIXES = [0xed, 0x1300] as const +const MULTICODEC_SR25519_PREFIXES = [0xef, 0x1303] as const + +type KeyTypeString = 'ed25519' | 'sr25519' | 'x25519' | 'secp256k1' + +export type KnownTypeString = KeyringPair['type'] | KeyTypeString + +function mapTypeStringToPrefixes(type: KnownTypeString): [number, number] { + switch (type) { + case 'ecdsa': + case 'secp256k1': + case 'ethereum': + return [...MULTICODEC_SECP256K1_PREFIXES] + case 'sr25519': + return [...MULTICODEC_SR25519_PREFIXES] + case 'x25519': + return [...MULTICODEC_X25519_PREFIXES] + case 'ed25519': + return [...MULTICODEC_ED25519_PREFIXES] + default: + throw new DidError(`The provided key type "${type}" is not supported.`) + } +} + +// TODO: This could also be exposed in a new release candidate of the `@kiltprotocol/jcs-data-integrity-proofs-common` package. +function multibase58BtcKeyBytesEncoding( + key: Uint8Array, + keyPrefix: number +): Base58BtcMultibaseString { + const varintEncodedPrefix = varint.encode(keyPrefix) + const prefixedKey = u8aConcat(varintEncodedPrefix, key) + const base58BtcEncodedKey = base58Encode(prefixedKey) + return `z${base58BtcEncodedKey}` +} + +export function encodeMultibaseKeypair( + args: Pick & { + type: KnownTypeString + } +): MultibasePublicKey +export function encodeMultibaseKeypair( + args: Pick & { + type: KnownTypeString + secretKey: Uint8Array + } +): MultibaseKeyPair +/** + * Calculate the Multikey representation of a keypair given its type and public/secret keys. + * + * @param keypair The input keypair to encode as Multikey. + * @param keypair.type The keypair type indicated by a type string. + * @param keypair.publicKey The keypair public key. + * @param keypair.secretKey Optionally, the keypair's secret key. + * @returns The Multikey representation (i.e., multicodec-prefixed, then multibase encoded) of the provided keypair. + */ +export function encodeMultibaseKeypair({ + type, + publicKey, + secretKey, +}: Pick & { + type: KnownTypeString + secretKey?: Uint8Array +}): MultibasePublicKey & Partial { + const [multiCodecPublicKeyPrefix, multiCodedSecretKeyPrefix] = + mapTypeStringToPrefixes(type) + + const keypair: MultibasePublicKey & Partial = { + publicKeyMultibase: multibase58BtcKeyBytesEncoding( + publicKey, + multiCodecPublicKeyPrefix + ), + } + if (secretKey) { + keypair.secretKeyMultibase = multibase58BtcKeyBytesEncoding( + secretKey, + multiCodedSecretKeyPrefix + ) + } + + return keypair +} + +const publicKeyPrefixes: Record = { + [MULTICODEC_SECP256K1_PREFIXES[0]]: 'secp256k1', + [MULTICODEC_X25519_PREFIXES[0]]: 'x25519', + [MULTICODEC_ED25519_PREFIXES[0]]: 'ed25519', + [MULTICODEC_SR25519_PREFIXES[0]]: 'sr25519', +} + +const secretKeyPrefixes: Record = { + [MULTICODEC_SECP256K1_PREFIXES[1]]: 'secp256k1', + [MULTICODEC_X25519_PREFIXES[1]]: 'x25519', + [MULTICODEC_ED25519_PREFIXES[1]]: 'ed25519', + [MULTICODEC_SR25519_PREFIXES[1]]: 'sr25519', +} + +/** + * Decode a Multikey representation of a verification method into its fundamental components: the public key and the key type. + * + * @param keyPairMultibase The verification method's public/private keys in Multikey format (i.e., multicodec-prefixed, then multibase encoded). + * @param keyPairMultibase.publicKeyMultibase The keypair's public key, encoded in Multikey format. + * @param keyPairMultibase.secretKeyMultibase Optionally, the keypair's secret key, encoded in Multikey format. + * @returns The decoded `publicKey` (and possibly `secretKey`) plus a key `type`. + */ +export function decodeMultibaseKeypair({ + publicKeyMultibase, + secretKeyMultibase, +}: MultibasePublicKey & Partial): Pick< + KeyringPair, + 'publicKey' +> & { type: KeyTypeString; secretKey?: Uint8Array } { + const { keyBytes, prefix } = decodeBase58BtcMultikey(publicKeyMultibase) + + const keyType = publicKeyPrefixes[prefix] + if (keyType === undefined) { + throw new DidError( + `Cannot decode key type for multibase key "${publicKeyMultibase}".` + ) + } + + const result: ReturnType = { + type: keyType, + publicKey: keyBytes, + } + + if (typeof secretKeyMultibase === 'string') { + const decodedKey = decodeBase58BtcMultikey(secretKeyMultibase) + const secretKeyType = secretKeyPrefixes[decodedKey.prefix] + if (secretKeyType !== keyType) { + throw new Error( + `Secret key type ${secretKeyType} (prefix ${decodedKey.prefix}) does not match public key type ${keyType} (prefix ${prefix})` + ) + } + result.secretKey = decodedKey.keyBytes + } + + return result +} diff --git a/packages/utils/src/Signers.ts b/packages/utils/src/Signers.ts index 5f00a6ffa..77f3063e1 100644 --- a/packages/utils/src/Signers.ts +++ b/packages/utils/src/Signers.ts @@ -39,10 +39,13 @@ import type { DidDocument, DidUrl, KeyringPair, + KiltKeyringPair, + MultibaseKeyPair, SignerInterface, UriFragment, } from '@kiltprotocol/types' import { makeKeypairFromUri } from './Crypto.js' +import { type KnownTypeString, encodeMultibaseKeypair } from './Multikey.js' import { DidError, NoSuitableSignerError } from './SDKErrors.js' export const ALGORITHMS = Object.freeze({ @@ -459,7 +462,10 @@ export function getPolkadotSigner( } const signature = await signer.sign({ data: signData }) // The signature is expected to be a SCALE enum, we must add a type prefix representing the signature algorithm - const prefixed = u8aConcat(TYPE_PREFIX[signer.algorithm], signature) + const prefixed = u8aConcat( + TYPE_PREFIX[signer.algorithm as keyof typeof TYPE_PREFIX], + signature + ) id += 1 return { id, @@ -469,20 +475,26 @@ export function getPolkadotSigner( } } -export function generateKeypair(args?: { - seed?: string - type?: T -}): Keypair & { type: T } +/** + * Generates a Multikey encoded keypair from a seed or mnemonic. + * + * @param args Optional generator arguments. + * @param args.seed A 32 byte hex-encoded and 0x-prefixed seed or 12-word mnemonic, optionally postfixed with a derivation path (e.g., `//authentication-key`). + * This is case-insensitive. + * @param args.type A string indicating desired key type. + * @returns A pair of `publicKeyMultibase` & `secretKeyMultibase`. + */ export function generateKeypair({ seed = randomAsHex(32), type = 'ed25519', }: { seed?: string - type?: string -} = {}): Keypair & { type: string } { - let typeForKeyring = type as KeyringPair['type'] + type?: KnownTypeString +} = {}): MultibaseKeyPair { + let typeForKeyring = type as KiltKeyringPair['type'] switch (type.toLowerCase()) { case 'secp256k1': + case 'ethereum': typeForKeyring = 'ecdsa' break case 'x25519': @@ -491,10 +503,8 @@ export function generateKeypair({ default: } - const keyRingPair = makeKeypairFromUri( - seed.toLowerCase(), - typeForKeyring as any - ) + const keyRingPair = makeKeypairFromUri(seed.toLowerCase(), typeForKeyring) + const { secretKey, publicKey } = extractPk(keyRingPair) - return { secretKey, publicKey, type } + return encodeMultibaseKeypair({ publicKey, secretKey, type }) } diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 6a0424605..ef52f484e 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -17,6 +17,7 @@ export * as DataUtils from './DataUtils.js' export * as SDKErrors from './SDKErrors.js' export * as JsonSchema from './json-schema/index.js' export * as Signers from './Signers.js' +export * as Multikey from './Multikey.js' export { Caip19, Caip2 } from './CAIP/index.js' export { ss58Format } from './ss58Format.js' export { cbor } from './cbor.js' diff --git a/yarn.lock b/yarn.lock index fd5ee1074..5a9ffb534 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2102,7 +2102,6 @@ __metadata: "@digitalbazaar/multikey-context": "npm:^2.0.1" "@digitalbazaar/security-context": "npm:^1.0.1" "@kiltprotocol/config": "workspace:*" - "@kiltprotocol/jcs-data-integrity-proofs-common": "npm:0.1.0-rc.4" "@kiltprotocol/types": "workspace:*" "@kiltprotocol/utils": "workspace:*" "@polkadot/api": "npm:^12.0.0" @@ -2112,7 +2111,6 @@ __metadata: "@polkadot/util-crypto": "npm:^13.0.0" rimraf: "npm:^3.0.2" typescript: "npm:^4.8.3" - varint: "npm:^6.0.0" peerDependencies: "@kiltprotocol/augment-api": "*" peerDependenciesMeta: @@ -2256,6 +2254,7 @@ __metadata: dependencies: "@kiltprotocol/eddsa-jcs-2022": "npm:0.1.0-rc.4" "@kiltprotocol/es256k-jcs-2023": "npm:0.1.0-rc.4" + "@kiltprotocol/jcs-data-integrity-proofs-common": "npm:0.1.0-rc.4" "@kiltprotocol/sr25519-jcs-2023": "npm:0.1.0-rc.4" "@kiltprotocol/types": "workspace:*" "@polkadot/api": "npm:^12.0.0" @@ -2268,6 +2267,7 @@ __metadata: tweetnacl: "npm:^1.0.3" typescript: "npm:^4.8.3" uuid: "npm:^10.0.0" + varint: "npm:^6.0.0" languageName: unknown linkType: soft From 2c26b0254db2a96bbe7cc99078a85722cd9ce430 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Thu, 18 Jul 2024 16:27:07 +0200 Subject: [PATCH 56/70] test: fix tests --- packages/did/src/Did.signature.spec.ts | 7 +- .../src/DidDetails/LightDidDetails.spec.ts | 29 +++-- .../did/src/DidResolver/DidResolver.spec.ts | 99 ++++++++--------- tests/integration/Did.spec.ts | 102 ++++++++++-------- tests/testUtils/TestUtils.ts | 11 +- 5 files changed, 129 insertions(+), 119 deletions(-) diff --git a/packages/did/src/Did.signature.spec.ts b/packages/did/src/Did.signature.spec.ts index 506644a5e..cce8f3423 100644 --- a/packages/did/src/Did.signature.spec.ts +++ b/packages/did/src/Did.signature.spec.ts @@ -14,7 +14,7 @@ import type { SignerInterface, } from '@kiltprotocol/types' -import { Crypto, SDKErrors, Signers } from '@kiltprotocol/utils' +import { Crypto, Multikey, SDKErrors, Signers } from '@kiltprotocol/utils' import { randomAsHex, randomAsU8a } from '@polkadot/util-crypto' import type { NewLightDidVerificationKey } from './DidDetails' @@ -26,7 +26,7 @@ import { verifyDidSignature, } from './Did.signature' import { resolve } from './DidResolver/DidResolver' -import { keypairToMultibaseKey, multibaseKeyToDidKey, parse } from './Did.utils' +import { multibaseKeyToDidKey, parse } from './Did.utils' import { createLightDidDocument } from './DidDetails' jest.mock('./DidResolver/DidResolver') @@ -292,7 +292,8 @@ describe('full DID', () => { { controller: `did:kilt:${keypair.address}`, id: `did:kilt:${keypair.address}#0x12345`, - publicKeyMultibase: keypairToMultibaseKey(keypair).publicKeyMultibase, + publicKeyMultibase: + Multikey.encodeMultibaseKeypair(keypair).publicKeyMultibase, type: 'Multikey', }, ], diff --git a/packages/did/src/DidDetails/LightDidDetails.spec.ts b/packages/did/src/DidDetails/LightDidDetails.spec.ts index 66b704dd3..61332a0f4 100644 --- a/packages/did/src/DidDetails/LightDidDetails.spec.ts +++ b/packages/did/src/DidDetails/LightDidDetails.spec.ts @@ -6,13 +6,12 @@ */ import type { DidDocument, Did, DidUrl } from '@kiltprotocol/types' - -import { Crypto } from '@kiltprotocol/utils' +import { Crypto, Multikey } from '@kiltprotocol/utils' import type { NewService } from './DidDetails.js' import type { CreateDocumentInput } from './LightDidDetails.js' -import { keypairToMultibaseKey, parse } from '../Did.utils.js' +import { parse } from '../Did.utils.js' import { createLightDidDocument, parseDocumentFromLightDid, @@ -62,19 +61,19 @@ describe('When creating an instance from the details', () => { { controller: did, id: `${did}#authentication`, - publicKeyMultibase: keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: authKey.publicKey, type: 'sr25519', - }), + }).publicKeyMultibase, type: 'Multikey', }, { controller: did, id: `${did}#encryption`, - publicKeyMultibase: keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: encKey.publicKey, type: 'x25519', - }), + }).publicKeyMultibase, type: 'Multikey', }, ], @@ -115,19 +114,19 @@ describe('When creating an instance from the details', () => { { controller: did, id: `${did}#authentication`, - publicKeyMultibase: keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: authKey.publicKey, type: 'ed25519', - }), + }).publicKeyMultibase, type: 'Multikey', }, { controller: did, id: `${did}#encryption`, - publicKeyMultibase: keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: encKey.publicKey, type: 'x25519', - }), + }).publicKeyMultibase, type: 'Multikey', }, ], @@ -197,19 +196,19 @@ describe('When creating an instance from a light DID', () => { { controller: did, id: `${did}#authentication`, - publicKeyMultibase: keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: authKey.publicKey, type: 'sr25519', - }), + }).publicKeyMultibase, type: 'Multikey', }, { controller: did, id: `${did}#encryption`, - publicKeyMultibase: keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: encKey.publicKey, type: 'x25519', - }), + }).publicKeyMultibase, type: 'Multikey', }, ], diff --git a/packages/did/src/DidResolver/DidResolver.spec.ts b/packages/did/src/DidResolver/DidResolver.spec.ts index a907c0cf6..2548e3947 100644 --- a/packages/did/src/DidResolver/DidResolver.spec.ts +++ b/packages/did/src/DidResolver/DidResolver.spec.ts @@ -17,7 +17,7 @@ import { VerificationMethod, UriFragment, } from '@kiltprotocol/types' -import { Crypto, cbor } from '@kiltprotocol/utils' +import { Crypto, Multikey, cbor } from '@kiltprotocol/utils' import { stringToU8a } from '@polkadot/util' import { ApiMocks, makeSigningKeyTool } from '../../../../tests/testUtils' @@ -87,10 +87,10 @@ function generateAuthenticationVerificationMethod( id: `${controller}#auth`, controller, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: new Uint8Array(32).fill(0), type: 'ed25519', - }), + }).publicKeyMultibase, } } @@ -101,10 +101,10 @@ function generateEncryptionVerificationMethod( id: `${controller}#enc`, controller, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: new Uint8Array(32).fill(1), type: 'x25519', - }), + }).publicKeyMultibase, } } @@ -115,10 +115,10 @@ function generateAssertionVerificationMethod( id: `${controller}#att`, controller, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: new Uint8Array(32).fill(2), type: 'sr25519', - }), + }).publicKeyMultibase, } } @@ -129,10 +129,10 @@ function generateCapabilityDelegationVerificationMethod( id: `${controller}#del`, controller, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: new Uint8Array(33).fill(3), type: 'ecdsa', - }), + }).publicKeyMultibase, } } @@ -313,10 +313,10 @@ describe('When resolving a full DID', () => { controller: fullDidWithAuthenticationKey, id: `${fullDidWithAuthenticationKey}#auth`, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ type: 'ed25519', publicKey: new Uint8Array(32).fill(0), - }), + }).publicKeyMultibase, }, ], }, @@ -363,37 +363,37 @@ describe('When resolving a full DID', () => { controller: fullDidWithAllKeys, id: `${fullDidWithAllKeys}#auth`, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ type: 'ed25519', publicKey: new Uint8Array(32).fill(0), - }), + }).publicKeyMultibase, }, { controller: fullDidWithAllKeys, id: `${fullDidWithAllKeys}#enc`, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ type: 'x25519', publicKey: new Uint8Array(32).fill(1), - }), + }).publicKeyMultibase, }, { controller: fullDidWithAllKeys, id: `${fullDidWithAllKeys}#att`, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ type: 'sr25519', publicKey: new Uint8Array(32).fill(2), - }), + }).publicKeyMultibase, }, { controller: fullDidWithAllKeys, id: `${fullDidWithAllKeys}#del`, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ type: 'ecdsa', publicKey: new Uint8Array(33).fill(3), - }), + }).publicKeyMultibase, }, ], }, @@ -435,10 +435,10 @@ describe('When resolving a full DID', () => { controller: fullDidWithServiceEndpoints, id: `${fullDidWithServiceEndpoints}#auth`, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ type: 'ed25519', publicKey: new Uint8Array(32).fill(0), - }), + }).publicKeyMultibase, }, ], service: [ @@ -488,10 +488,10 @@ describe('When resolving a full DID', () => { controller: didWithAuthenticationKey, id: `${didWithAuthenticationKey}#auth`, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ type: 'ed25519', publicKey: new Uint8Array(32).fill(0), - }), + }).publicKeyMultibase, }, ], alsoKnownAs: ['w3n:w3nick'], @@ -507,9 +507,9 @@ describe('When resolving a full DID', () => { augmentedApi.createType('Option', null) ) const randomKeypair = (await makeSigningKeyTool()).authentication[0] - const randomDid = Did.getFullDidFromVerificationMethod({ - publicKeyMultibase: Did.keypairToMultibaseKey(randomKeypair), - }) + const randomDid = Did.getFullDidFromVerificationMethod( + Multikey.encodeMultibaseKeypair(randomKeypair) + ) expect(await Did.resolve(randomDid)).toStrictEqual({ didDocumentMetadata: {}, didResolutionMetadata: { error: 'notFound' }, @@ -563,10 +563,10 @@ describe('When resolving a light DID', () => { controller: lightDidWithAuthenticationKey.id, id: `${lightDidWithAuthenticationKey.id}#authentication`, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ ...authKey, type: 'sr25519', - }), + }).publicKeyMultibase, }, ], }, @@ -594,16 +594,17 @@ describe('When resolving a light DID', () => { controller: lightDid.id, id: `${lightDid.id}#authentication`, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ ...authKey, type: 'sr25519', - }), + }).publicKeyMultibase, }, { controller: lightDid.id, id: `${lightDid.id}#encryption`, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey(encryptionKey), + publicKeyMultibase: + Multikey.encodeMultibaseKeypair(encryptionKey).publicKeyMultibase, }, ], service: [ @@ -737,10 +738,10 @@ describe('DID Resolution compliance', () => { id: `${did}#auth`, controller: did, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: new Uint8Array(32).fill(0), type: 'ed25519', - }), + }).publicKeyMultibase, }, ], }, @@ -786,10 +787,10 @@ describe('DID Resolution compliance', () => { id: `${did}#auth`, controller: did, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: new Uint8Array(32).fill(0), type: 'ed25519', - }), + }).publicKeyMultibase, }, ], }) @@ -817,10 +818,10 @@ describe('DID Resolution compliance', () => { id: `${did}#auth`, controller: did, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: new Uint8Array(32).fill(0), type: 'ed25519', - }), + }).publicKeyMultibase, }, ], '@context': [Did.W3C_DID_CONTEXT_URL, Did.KILT_DID_CONTEXT_URL], @@ -847,10 +848,10 @@ describe('DID Resolution compliance', () => { id: `${did}#auth`, controller: did, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: new Uint8Array(32).fill(0), type: 'ed25519', - }), + }).publicKeyMultibase, }, ], }) @@ -911,10 +912,10 @@ describe('DID Resolution compliance', () => { id: `${did}#auth`, controller: did, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: new Uint8Array(32).fill(0), type: 'ed25519', - }), + }).publicKeyMultibase, }, ], }, @@ -938,10 +939,10 @@ describe('DID Resolution compliance', () => { id: `${did}#auth`, controller: did, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: new Uint8Array(32).fill(0), type: 'ed25519', - }), + }).publicKeyMultibase, }, ], '@context': [Did.W3C_DID_CONTEXT_URL, Did.KILT_DID_CONTEXT_URL], @@ -965,10 +966,10 @@ describe('DID Resolution compliance', () => { id: `${did}#auth`, controller: did, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: new Uint8Array(32).fill(0), type: 'ed25519', - }), + }).publicKeyMultibase, }, ], }) @@ -988,10 +989,10 @@ describe('DID Resolution compliance', () => { controller: 'did:kilt:4r1WkS3t8rbCb11H8t3tJvGVCynwDXSUBiuGB6sLRHzCLCjs', type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: new Uint8Array(32).fill(0), type: 'ed25519', - }), + }).publicKeyMultibase, }, }) }) @@ -1072,10 +1073,10 @@ describe('DID Resolution compliance', () => { id: `${did}#auth`, controller: did, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: new Uint8Array(32).fill(0), type: 'ed25519', - }), + }).publicKeyMultibase, }, ], }, diff --git a/tests/integration/Did.spec.ts b/tests/integration/Did.spec.ts index a6f6cdab4..0637d2bd8 100644 --- a/tests/integration/Did.spec.ts +++ b/tests/integration/Did.spec.ts @@ -21,7 +21,7 @@ import { CType, DelegationNode } from '@kiltprotocol/credentials' import * as Did from '@kiltprotocol/did' import { disconnect } from '@kiltprotocol/sdk-js' import { Permission } from '@kiltprotocol/types' -import { UUID } from '@kiltprotocol/utils' +import { Multikey, UUID } from '@kiltprotocol/utils' import type { KeyTool } from '../testUtils/index.js' @@ -137,10 +137,10 @@ describe('write and didDeleteTx', () => { controller: fullDid, type: 'Multikey', // We cannot match the ID of the key because it will be defined by the blockchain while saving - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ type: 'sr25519', publicKey: authPublicKey, - }), + }).publicKeyMultibase, }), ], }) @@ -370,13 +370,15 @@ describe('DID migration', () => { controller: migratedFullDid, type: 'Multikey', // We cannot match the ID of the key because it will be defined by the blockchain while saving - publicKeyMultibase: Did.keypairToMultibaseKey(authentication[0]), + publicKeyMultibase: Multikey.encodeMultibaseKeypair(authentication[0]) + .publicKeyMultibase, }), expect.objectContaining(>{ controller: migratedFullDid, type: 'Multikey', // We cannot match the ID of the key because it will be defined by the blockchain while saving - publicKeyMultibase: Did.keypairToMultibaseKey(keyAgreement[0]), + publicKeyMultibase: Multikey.encodeMultibaseKeypair(keyAgreement[0]) + .publicKeyMultibase, }), ]), }) @@ -427,7 +429,8 @@ describe('DID migration', () => { controller: migratedFullDid, type: 'Multikey', // We cannot match the ID of the key because it will be defined by the blockchain while saving - publicKeyMultibase: Did.keypairToMultibaseKey(authentication[0]), + publicKeyMultibase: Multikey.encodeMultibaseKeypair(authentication[0]) + .publicKeyMultibase, }), ], }) @@ -492,13 +495,15 @@ describe('DID migration', () => { controller: migratedFullDid, type: 'Multikey', // We cannot match the ID of the key because it will be defined by the blockchain while saving - publicKeyMultibase: Did.keypairToMultibaseKey(authentication[0]), + publicKeyMultibase: Multikey.encodeMultibaseKeypair(authentication[0]) + .publicKeyMultibase, }), expect.objectContaining(>{ controller: migratedFullDid, type: 'Multikey', // We cannot match the ID of the key because it will be defined by the blockchain while saving - publicKeyMultibase: Did.keypairToMultibaseKey(keyAgreement[0]), + publicKeyMultibase: Multikey.encodeMultibaseKeypair(keyAgreement[0]) + .publicKeyMultibase, }), ]), service: [ @@ -563,9 +568,9 @@ describe('DID authorization', () => { await submitTx(createTx, paymentAccount) const didLinkedInfo = await api.call.did.query( Did.toChain( - Did.getFullDidFromVerificationMethod({ - publicKeyMultibase: Did.keypairToMultibaseKey(authentication[0]), - }) + Did.getFullDidFromVerificationMethod( + Multikey.encodeMultibaseKeypair(authentication[0]) + ) ) ) did = Did.linkedInfoFromChain(didLinkedInfo).document @@ -674,9 +679,9 @@ describe('DID management batching', () => { await submitTx(extrinsic, paymentAccount) const fullDidLinkedInfo = await api.call.did.query( Did.toChain( - Did.getFullDidFromVerificationMethod({ - publicKeyMultibase: Did.keypairToMultibaseKey(authentication[0]), - }) + Did.getFullDidFromVerificationMethod( + Multikey.encodeMultibaseKeypair(authentication[0]) + ) ) ) const { document: fullDid } = Did.linkedInfoFromChain(fullDidLinkedInfo) @@ -688,52 +693,54 @@ describe('DID management batching', () => { // Authentication controller: fullDid.id, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey(authentication[0]), + publicKeyMultibase: Multikey.encodeMultibaseKeypair( + authentication[0] + ).publicKeyMultibase, }), // Assertion method expect.objectContaining({ controller: fullDid.id, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ type: 'sr25519', publicKey: new Uint8Array(32).fill(1), - }), + }).publicKeyMultibase, }), // Capability delegation expect.objectContaining({ controller: fullDid.id, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ type: 'ecdsa', publicKey: new Uint8Array(33).fill(1), - }), + }).publicKeyMultibase, }), // Key agreement 1 expect.objectContaining({ controller: fullDid.id, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ type: 'x25519', publicKey: new Uint8Array(32).fill(1), - }), + }).publicKeyMultibase, }), // Key agreement 2 expect.objectContaining({ controller: fullDid.id, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ type: 'x25519', publicKey: new Uint8Array(32).fill(2), - }), + }).publicKeyMultibase, }), // Key agreement 3 expect.objectContaining({ controller: fullDid.id, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ type: 'x25519', publicKey: new Uint8Array(32).fill(3), - }), + }).publicKeyMultibase, }), ]) ) @@ -778,9 +785,9 @@ describe('DID management batching', () => { const fullDidLinkedInfo = await api.call.did.query( Did.toChain( - Did.getFullDidFromVerificationMethod({ - publicKeyMultibase: Did.keypairToMultibaseKey(didAuthKey), - }) + Did.getFullDidFromVerificationMethod( + Multikey.encodeMultibaseKeypair(didAuthKey) + ) ) ) const { document: fullDid } = Did.linkedInfoFromChain(fullDidLinkedInfo) @@ -792,7 +799,8 @@ describe('DID management batching', () => { expect.objectContaining(>{ controller: fullDid.id, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey(didAuthKey), + publicKeyMultibase: + Multikey.encodeMultibaseKeypair(didAuthKey).publicKeyMultibase, }), ], }) @@ -853,9 +861,9 @@ describe('DID management batching', () => { const initialFullDidLinkedInfo = await api.call.did.query( Did.toChain( - Did.getFullDidFromVerificationMethod({ - publicKeyMultibase: Did.keypairToMultibaseKey(authentication[0]), - }) + Did.getFullDidFromVerificationMethod( + Multikey.encodeMultibaseKeypair(authentication[0]) + ) ) ) const { document: initialFullDid } = Did.linkedInfoFromChain( @@ -908,10 +916,10 @@ describe('DID management batching', () => { expect.objectContaining(>{ controller: finalFullDid.id, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: keypair.publicKey, type: 'sr25519', - }), + }).publicKeyMultibase, }), ], }) @@ -937,9 +945,9 @@ describe('DID management batching', () => { const initialFullDidLinkedInfo = await api.call.did.query( Did.toChain( - Did.getFullDidFromVerificationMethod({ - publicKeyMultibase: Did.keypairToMultibaseKey(authentication[0]), - }) + Did.getFullDidFromVerificationMethod( + Multikey.encodeMultibaseKeypair(authentication[0]) + ) ) ) const { document: initialFullDid } = Did.linkedInfoFromChain( @@ -986,10 +994,10 @@ describe('DID management batching', () => { expect.objectContaining(>{ controller: finalFullDid.id, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey({ + publicKeyMultibase: Multikey.encodeMultibaseKeypair({ publicKey: newAuthKey.publicKey, type: 'ed25519', - }), + }).publicKeyMultibase, }), ], service: [ @@ -1033,9 +1041,9 @@ describe('DID management batching', () => { await submitTx(tx, paymentAccount) const fullDidLinkedInfo = await api.call.did.query( Did.toChain( - Did.getFullDidFromVerificationMethod({ - publicKeyMultibase: Did.keypairToMultibaseKey(authentication[0]), - }) + Did.getFullDidFromVerificationMethod( + Multikey.encodeMultibaseKeypair(authentication[0]) + ) ) ) const { document: fullDid } = Did.linkedInfoFromChain(fullDidLinkedInfo) @@ -1075,7 +1083,9 @@ describe('DID management batching', () => { // Authentication and assertionMethod controller: fullDid.id, type: 'Multikey', - publicKeyMultibase: Did.keypairToMultibaseKey(authentication[0]), + publicKeyMultibase: Multikey.encodeMultibaseKeypair( + authentication[0] + ).publicKeyMultibase, }), ], // Old service maintained @@ -1117,9 +1127,9 @@ describe('DID management batching', () => { await submitTx(createTx, paymentAccount) const fullDidLinkedInfo = await api.call.did.query( Did.toChain( - Did.getFullDidFromVerificationMethod({ - publicKeyMultibase: Did.keypairToMultibaseKey(authentication[0]), - }) + Did.getFullDidFromVerificationMethod( + Multikey.encodeMultibaseKeypair(authentication[0]) + ) ) ) const { document: fullDid } = Did.linkedInfoFromChain(fullDidLinkedInfo) diff --git a/tests/testUtils/TestUtils.ts b/tests/testUtils/TestUtils.ts index 0e1609695..7acae55d7 100644 --- a/tests/testUtils/TestUtils.ts +++ b/tests/testUtils/TestUtils.ts @@ -22,7 +22,7 @@ import type { } from '@kiltprotocol/types' import { ConfigService } from '@kiltprotocol/config' import { Blockchain } from '@kiltprotocol/chain-helpers' -import { Crypto, Signers, SDKErrors } from '@kiltprotocol/utils' +import { Crypto, Signers, SDKErrors, Multikey } from '@kiltprotocol/utils' import { type BaseNewDidKey, type ChainDidKey, @@ -35,7 +35,6 @@ import { getStoreTx, didKeyToVerificationMethod, createLightDidDocument, - keypairToMultibaseKey, getFullDidFromVerificationMethod, multibaseKeyToDidKey, isValidDidVerificationType, @@ -248,12 +247,12 @@ export async function createLocalDemoFullDidFromKeypair( publicKey, id: authKeyFragment, } = makeDidKeyFromKeypair(keypair) - const id = getFullDidFromVerificationMethod({ - publicKeyMultibase: keypairToMultibaseKey({ + const id = getFullDidFromVerificationMethod( + Multikey.encodeMultibaseKeypair({ type: keyType, publicKey, - }), - }) + }) + ) const authKeyId = `${id}${authKeyFragment}` as const const result: DidDocument = { From 9f0819b7262562bea2e461209b3931dabe605752 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Mon, 22 Jul 2024 13:13:41 +0200 Subject: [PATCH 57/70] fix: add missing implementation on getSignersForKeypair --- packages/utils/src/Multikey.ts | 16 +++++++++++++--- packages/utils/src/Signers.ts | 23 +++++++---------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/utils/src/Multikey.ts b/packages/utils/src/Multikey.ts index d1b0d82b4..846285cd0 100644 --- a/packages/utils/src/Multikey.ts +++ b/packages/utils/src/Multikey.ts @@ -60,14 +60,14 @@ function multibase58BtcKeyBytesEncoding( export function encodeMultibaseKeypair( args: Pick & { type: KnownTypeString + secretKey: Uint8Array } -): MultibasePublicKey +): MultibaseKeyPair export function encodeMultibaseKeypair( args: Pick & { type: KnownTypeString - secretKey: Uint8Array } -): MultibaseKeyPair +): MultibasePublicKey /** * Calculate the Multikey representation of a keypair given its type and public/secret keys. * @@ -118,6 +118,16 @@ const secretKeyPrefixes: Record = { [MULTICODEC_SR25519_PREFIXES[1]]: 'sr25519', } +export function decodeMultibaseKeypair(keypair: MultibaseKeyPair): Pick< + KeyringPair, + 'publicKey' +> & { + secretKey: Uint8Array + type: KeyTypeString +} +export function decodeMultibaseKeypair( + keyPairPublicKey: MultibasePublicKey +): Pick & { type: KeyTypeString } /** * Decode a Multikey representation of a verification method into its fundamental components: the public key and the key type. * diff --git a/packages/utils/src/Signers.ts b/packages/utils/src/Signers.ts index 77f3063e1..27c95f7b0 100644 --- a/packages/utils/src/Signers.ts +++ b/packages/utils/src/Signers.ts @@ -45,7 +45,11 @@ import type { UriFragment, } from '@kiltprotocol/types' import { makeKeypairFromUri } from './Crypto.js' -import { type KnownTypeString, encodeMultibaseKeypair } from './Multikey.js' +import { + type KnownTypeString, + decodeMultibaseKeypair, + encodeMultibaseKeypair, +} from './Multikey.js' import { DidError, NoSuitableSignerError } from './SDKErrors.js' export const ALGORITHMS = Object.freeze({ @@ -229,25 +233,12 @@ export async function getSignersForKeypair({ type, }: { id?: Id - keypair: - | Keypair - | KeyringPair - | { - secretKeyMultibase: `z${string}` - publicKeyMultibase: `z${string}` - } + keypair: Keypair | KeyringPair | MultibaseKeyPair type?: string }): Promise>> { let pair: KeyringPair | (Keypair & { type: string }) if ('publicKeyMultibase' in keypair) { - throw new Error('not implemented') - // const { publicKey, keyType } = multibaseKeyToDidKey( - // keypair.publicKeyMultibase - // ) - // const { publicKey: secretKey } = decodeMultikeyVerificationMethod({ - // publicKeyMultibase: keypair.secretKeyMultibase, - // }) - // pair = { publicKey, secretKey, type: keyType } + pair = decodeMultibaseKeypair(keypair) } else if ('type' in keypair) { pair = keypair } else if (type) { From b00ada17ae882b101b2743ad619079a7cf28b7c8 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Mon, 22 Jul 2024 13:15:08 +0200 Subject: [PATCH 58/70] test: use generateKeypair in did helpers integration tests --- tests/integration/didHelpers.spec.ts | 29 +++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/integration/didHelpers.spec.ts b/tests/integration/didHelpers.spec.ts index 628734faa..df91f491d 100644 --- a/tests/integration/didHelpers.spec.ts +++ b/tests/integration/didHelpers.spec.ts @@ -8,11 +8,13 @@ import type { ApiPromise } from '@polkadot/api' import { CType } from '@kiltprotocol/credentials' -import { DidHelpers, disconnect } from '@kiltprotocol/sdk-js' +import { getFullDidFromVerificationMethod } from '@kiltprotocol/did' +import { DidHelpers, disconnect, generateKeypair } from '@kiltprotocol/sdk-js' import type { DidDocument, KeyringPair, KiltKeyringPair, + MultibaseKeyPair, Service, } from '@kiltprotocol/types' import { Crypto } from '@kiltprotocol/utils' @@ -31,13 +33,13 @@ beforeAll(async () => { // Create did on chain describe('create and deactivate DID', () => { - let kp: KeyringPair + let kp: MultibaseKeyPair let didDocument: DidDocument beforeAll(() => { - kp = Crypto.makeKeypairFromUri( - 'build hill second flame trigger simple rigid cabbage phrase evolve final eight', - 'sr25519' - ) + kp = generateKeypair({ + seed: 'build hill second flame trigger simple rigid cabbage phrase evolve final eight', + type: 'sr25519', + }) }) it('creates a DID', async () => { @@ -50,8 +52,9 @@ describe('create and deactivate DID', () => { expect(result.status).toBe('confirmed') didDocument = result.asConfirmed.didDocument + const did = getFullDidFromVerificationMethod(kp) expect(didDocument).toMatchObject({ - id: `did:kilt:${kp.address}`, + id: did, verificationMethod: expect.any(Array), authentication: expect.any(Array), }) @@ -77,10 +80,10 @@ describe('create and deactivate DID', () => { }) describe('w3ns', () => { - let keypair: KeyringPair + let keypair: MultibaseKeyPair let didDocument: DidDocument beforeAll(async () => { - keypair = Crypto.makeKeypairFromUri('//Blob') + keypair = generateKeypair({ seed: '//Blob' }) const result = await DidHelpers.createDid({ api, signers: [keypair], @@ -137,10 +140,10 @@ describe('w3ns', () => { }) describe('services', () => { - let keypair: KeyringPair + let keypair: MultibaseKeyPair let didDocument: DidDocument beforeAll(async () => { - keypair = Crypto.makeKeypairFromUri('//Services') + keypair = generateKeypair({ seed: '//Services' }) const result = await DidHelpers.createDid({ api, signers: [keypair], @@ -327,10 +330,10 @@ describe('verification methods', () => { }) describe('transact', () => { - let keypair: KeyringPair + let keypair: MultibaseKeyPair let didDocument: DidDocument beforeAll(async () => { - keypair = Crypto.makeKeypairFromUri('//Transact') + keypair = generateKeypair({ seed: '//Transact' }) didDocument = ( await DidHelpers.createDid({ api, From e8c065415c6b4875d60408d4c02804b2955e53d5 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab <31316147+abdulmth@users.noreply.github.com> Date: Mon, 22 Jul 2024 13:28:45 +0200 Subject: [PATCH 59/70] Update packages/sdk-js/src/DidHelpers/checkResult.ts Co-authored-by: Raphael Flechtner <39338561+rflechtner@users.noreply.github.com> --- packages/sdk-js/src/DidHelpers/checkResult.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk-js/src/DidHelpers/checkResult.ts b/packages/sdk-js/src/DidHelpers/checkResult.ts index 93b959666..fc3c1188e 100644 --- a/packages/sdk-js/src/DidHelpers/checkResult.ts +++ b/packages/sdk-js/src/DidHelpers/checkResult.ts @@ -264,8 +264,8 @@ export async function checkResultImpl( toJSON() { const clone = { ...this } as any clone.block = { + ...clone.block, number: clone.block.number.toString(), - hash: this.block.hash, } return clone }, From 1762a2999499fc56d727925a6aad5862b5f00a6c Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Mon, 22 Jul 2024 13:29:51 +0200 Subject: [PATCH 60/70] chore: improvement --- packages/sdk-js/src/DidHelpers/checkResult.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk-js/src/DidHelpers/checkResult.ts b/packages/sdk-js/src/DidHelpers/checkResult.ts index fc3c1188e..62b120897 100644 --- a/packages/sdk-js/src/DidHelpers/checkResult.ts +++ b/packages/sdk-js/src/DidHelpers/checkResult.ts @@ -228,8 +228,8 @@ export async function checkResultImpl( toJSON() { const clone = { ...this } as any clone.block = { + ...clone.block, number: clone.block.number.toString(), - hash: this.block.hash, } return clone }, From 975886df66e9b706842591ada9265756288bd9d7 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Mon, 22 Jul 2024 15:06:45 +0200 Subject: [PATCH 61/70] feat: implement toJSON for root --- packages/sdk-js/src/DidHelpers/checkResult.ts | 33 +++++++++++++++++++ packages/sdk-js/src/DidHelpers/interfaces.ts | 1 + .../sdk-js/src/DidHelpers/transact.spec.ts | 4 +++ 3 files changed, 38 insertions(+) diff --git a/packages/sdk-js/src/DidHelpers/checkResult.ts b/packages/sdk-js/src/DidHelpers/checkResult.ts index 62b120897..cb8e545bc 100644 --- a/packages/sdk-js/src/DidHelpers/checkResult.ts +++ b/packages/sdk-js/src/DidHelpers/checkResult.ts @@ -231,6 +231,7 @@ export async function checkResultImpl( ...clone.block, number: clone.block.number.toString(), } + delete clone.toJSON return clone }, } @@ -267,9 +268,41 @@ export async function checkResultImpl( ...clone.block, number: clone.block.number.toString(), } + delete clone.toJSON return clone }, } }, + toJSON() { + switch (status) { + case 'confirmed': { + return { + status, + value: (this as TransactionResult).asConfirmed.toJSON(), + } + } + case 'failed': { + return { + status, + value: (this as TransactionResult).asFailed.toJSON(), + } + } + case 'rejected': { + return { + status, + value: (this as TransactionResult).asRejected, + } + } + case 'unknown': { + return { + status, + value: (this as TransactionResult).asUnknown, + } + } + default: { + throw new Error('invalid status') + } + } + }, } } diff --git a/packages/sdk-js/src/DidHelpers/interfaces.ts b/packages/sdk-js/src/DidHelpers/interfaces.ts index 0540eab6f..3dd2a8e24 100644 --- a/packages/sdk-js/src/DidHelpers/interfaces.ts +++ b/packages/sdk-js/src/DidHelpers/interfaces.ts @@ -47,6 +47,7 @@ export interface TransactionResult { error: Error txHash: HexString } + toJSON: () => any } export interface TransactionHandlers { diff --git a/packages/sdk-js/src/DidHelpers/transact.spec.ts b/packages/sdk-js/src/DidHelpers/transact.spec.ts index 373e20c6b..3101a6fcc 100644 --- a/packages/sdk-js/src/DidHelpers/transact.spec.ts +++ b/packages/sdk-js/src/DidHelpers/transact.spec.ts @@ -110,5 +110,9 @@ describe('transact', () => { const resultRebuiltObj = JSON.parse(resultStringified) expect(BigInt(resultRebuiltObj.block.number)).toBe(BigInt(1000)) expect(typeof confirmed.block.number).toBe('bigint') + expect(result.toJSON()).toStrictEqual({ + status: 'confirmed', + value: confirmed.toJSON(), + }) }) }) From 4b2718fb5da2e034955fa10e3baf79a4228f0108 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Mon, 22 Jul 2024 15:07:31 +0200 Subject: [PATCH 62/70] fix: lint --- packages/sdk-js/src/DidHelpers/interfaces.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk-js/src/DidHelpers/interfaces.ts b/packages/sdk-js/src/DidHelpers/interfaces.ts index 3dd2a8e24..72db1a80b 100644 --- a/packages/sdk-js/src/DidHelpers/interfaces.ts +++ b/packages/sdk-js/src/DidHelpers/interfaces.ts @@ -47,7 +47,7 @@ export interface TransactionResult { error: Error txHash: HexString } - toJSON: () => any + toJSON: () => any } export interface TransactionHandlers { From f473255c383cf38ee30bd97df794ee28f2c8e958 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Mon, 22 Jul 2024 19:14:20 +0200 Subject: [PATCH 63/70] chore: remove duplicated code --- packages/chain-helpers/src/blockchain/Blockchain.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/chain-helpers/src/blockchain/Blockchain.ts b/packages/chain-helpers/src/blockchain/Blockchain.ts index c22e90e56..ea10c1367 100644 --- a/packages/chain-helpers/src/blockchain/Blockchain.ts +++ b/packages/chain-helpers/src/blockchain/Blockchain.ts @@ -216,13 +216,7 @@ export async function signAndSubmitTx( ...opts }: Partial & Partial<{ tip: AnyNumber }> = {} ): Promise { - const signedTx = - 'address' in signer - ? await tx.signAsync(signer, { tip }) - : await tx.signAsync(signer.id, { - tip, - signer: Signers.getPolkadotSigner([signer]), - }) + const signedTx = await signTx(tx, signer, { tip }) return submitSignedTx(signedTx, opts) } From fb44a82c96317a2bd142fa8499bf3ab90bef6265 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 23 Jul 2024 10:18:05 +0200 Subject: [PATCH 64/70] chore: no magic numbers --- packages/did/src/Did.utils.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/did/src/Did.utils.ts b/packages/did/src/Did.utils.ts index e4a11a68a..c2bc594f8 100644 --- a/packages/did/src/Did.utils.ts +++ b/packages/did/src/Did.utils.ts @@ -127,6 +127,9 @@ type DecodedVerificationMethod = { keyType: DidVerificationMethodType } +const KEY_LENGTH_ECDSA = 33 +const KEY_LENGTH_OTHER = 32 + /** * Decode a Multikey representation of a verification method into its fundamental components: the public key and the key type. * @@ -139,7 +142,8 @@ export function multibaseKeyToDidKey( const { publicKey, type } = Multikey.decodeMultibaseKeypair({ publicKeyMultibase, }) - const expectedPublicKeyLength = type === 'secp256k1' ? 33 : 32 + const expectedPublicKeyLength = + type === 'secp256k1' ? KEY_LENGTH_ECDSA : KEY_LENGTH_OTHER if (publicKey.length !== expectedPublicKeyLength) { throw new SDKErrors.DidError( `Key of type "${type}" is expected to be ${expectedPublicKeyLength} bytes long. Provided key is ${publicKey.length} bytes long instead.` @@ -254,7 +258,8 @@ export function getAddressFromVerificationMethod({ // Otherwise it’s ecdsa. // Taken from https://github.com/polkadot-js/common/blob/master/packages/keyring/src/pair/index.ts#L44 - const address = publicKey.length > 32 ? blake2AsU8a(publicKey) : publicKey + const address = + publicKey.length > KEY_LENGTH_OTHER ? blake2AsU8a(publicKey) : publicKey return encodeAddress(address, ss58Format) } From d69481d40bf17e19c54429c89df106f983c9919b Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 23 Jul 2024 10:41:27 +0200 Subject: [PATCH 65/70] docs: improve docstrings --- packages/utils/src/Multikey.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/Multikey.ts b/packages/utils/src/Multikey.ts index 846285cd0..810eedc2e 100644 --- a/packages/utils/src/Multikey.ts +++ b/packages/utils/src/Multikey.ts @@ -69,13 +69,13 @@ export function encodeMultibaseKeypair( } ): MultibasePublicKey /** - * Calculate the Multikey representation of a keypair given its type and public/secret keys. + * Create a Multikey representation of a keypair, encoded in multibase-base58-btc, given its type and public/secret keys. * * @param keypair The input keypair to encode as Multikey. * @param keypair.type The keypair type indicated by a type string. * @param keypair.publicKey The keypair public key. * @param keypair.secretKey Optionally, the keypair's secret key. - * @returns The Multikey representation (i.e., multicodec-prefixed, then multibase encoded) of the provided keypair. + * @returns The Multikey representation (i.e., multicodec-prefixed, then base58-btc multibase encoded) of the provided keypair. */ export function encodeMultibaseKeypair({ type, @@ -130,6 +130,7 @@ export function decodeMultibaseKeypair( ): Pick & { type: KeyTypeString } /** * Decode a Multikey representation of a verification method into its fundamental components: the public key and the key type. + * Note that only base58-btc multibase encoding is currently supported. * * @param keyPairMultibase The verification method's public/private keys in Multikey format (i.e., multicodec-prefixed, then multibase encoded). * @param keyPairMultibase.publicKeyMultibase The keypair's public key, encoded in Multikey format. From 1bdf0d8ca39040dfe041f7839520cdaa52d46c76 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Tue, 23 Jul 2024 11:01:22 +0200 Subject: [PATCH 66/70] docs: streamline typings and add overload docstrings for en/decodeMultibaseKeypair --- packages/utils/src/Multikey.ts | 87 ++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/packages/utils/src/Multikey.ts b/packages/utils/src/Multikey.ts index 810eedc2e..ce3a486ef 100644 --- a/packages/utils/src/Multikey.ts +++ b/packages/utils/src/Multikey.ts @@ -57,34 +57,46 @@ function multibase58BtcKeyBytesEncoding( return `z${base58BtcEncodedKey}` } -export function encodeMultibaseKeypair( - args: Pick & { - type: KnownTypeString - secretKey: Uint8Array - } -): MultibaseKeyPair -export function encodeMultibaseKeypair( - args: Pick & { - type: KnownTypeString - } -): MultibasePublicKey +type TypedKeypairWithOptionalSecretKey = { + publicKey: Uint8Array + secretKey?: Uint8Array + type: KeyTypes +} + +type TypedKeypair = Required< + TypedKeypairWithOptionalSecretKey +> + /** * Create a Multikey representation of a keypair, encoded in multibase-base58-btc, given its type and public/secret keys. * * @param keypair The input keypair to encode as Multikey. * @param keypair.type The keypair type indicated by a type string. - * @param keypair.publicKey The keypair public key. - * @param keypair.secretKey Optionally, the keypair's secret key. + * @param keypair.publicKey The keypair's public key bytes. + * @param keypair.secretKey The keypair's secret key bytes. * @returns The Multikey representation (i.e., multicodec-prefixed, then base58-btc multibase encoded) of the provided keypair. */ +export function encodeMultibaseKeypair( + args: TypedKeypair +): MultibaseKeyPair +/** + * Create a Multikey representation of a keypair's public key, encoded in multibase-base58-btc, given its type and public key bytes. + * + * @param keypair The input keypair to encode as Multikey. + * @param keypair.type The keypair type indicated by a type string. + * @param keypair.publicKey The keypair's public key bytes. + * @returns The Multikey representation (i.e., multicodec-prefixed, then base58-btc multibase encoded) of the public key. + */ +export function encodeMultibaseKeypair( + args: Pick, 'publicKey' | 'type'> +): MultibasePublicKey +// eslint-disable-next-line jsdoc/require-jsdoc -- Docs are provided to overloads. export function encodeMultibaseKeypair({ type, publicKey, secretKey, -}: Pick & { - type: KnownTypeString - secretKey?: Uint8Array -}): MultibasePublicKey & Partial { +}: TypedKeypairWithOptionalSecretKey): MultibasePublicKey & + Partial { const [multiCodecPublicKeyPrefix, multiCodedSecretKeyPrefix] = mapTypeStringToPrefixes(type) @@ -118,32 +130,35 @@ const secretKeyPrefixes: Record = { [MULTICODEC_SR25519_PREFIXES[1]]: 'sr25519', } -export function decodeMultibaseKeypair(keypair: MultibaseKeyPair): Pick< - KeyringPair, - 'publicKey' -> & { - secretKey: Uint8Array - type: KeyTypeString -} -export function decodeMultibaseKeypair( - keyPairPublicKey: MultibasePublicKey -): Pick & { type: KeyTypeString } /** - * Decode a Multikey representation of a verification method into its fundamental components: the public key and the key type. + * Decodes a Multikey representation of a key pair, returning the public- and secret key bytes and the key type. * Note that only base58-btc multibase encoding is currently supported. * - * @param keyPairMultibase The verification method's public/private keys in Multikey format (i.e., multicodec-prefixed, then multibase encoded). + * @param keyPairMultibase A key pair represented in Multikey format (i.e., multicodec-prefixed, then multibase encoded). * @param keyPairMultibase.publicKeyMultibase The keypair's public key, encoded in Multikey format. - * @param keyPairMultibase.secretKeyMultibase Optionally, the keypair's secret key, encoded in Multikey format. - * @returns The decoded `publicKey` (and possibly `secretKey`) plus a key `type`. + * @param keyPairMultibase.secretKeyMultibase The keypair's secret key, encoded in Multikey format. + * @returns The decoded `publicKey` and `secretKey` bytes plus a key `type`. */ +export function decodeMultibaseKeypair( + keypair: MultibaseKeyPair +): TypedKeypair +/** + * Decodes a Multikey representation of a public key/verification method, returning the publics key bytes and the key type. + * Note that only base58-btc multibase encoding is currently supported. + * + * @param keyPairMultibase A verification method or comparable public key representation. + * @param keyPairMultibase.publicKeyMultibase The keypair's public key, multicodec-prefixed, then multibase encoded. + * @returns The decoded `publicKey` bytes plus a key `type`. + */ +export function decodeMultibaseKeypair( + keyPairPublicKey: MultibasePublicKey +): Pick, 'publicKey' | 'type'> +// eslint-disable-next-line jsdoc/require-jsdoc -- Docs are provided to overloads. export function decodeMultibaseKeypair({ publicKeyMultibase, secretKeyMultibase, -}: MultibasePublicKey & Partial): Pick< - KeyringPair, - 'publicKey' -> & { type: KeyTypeString; secretKey?: Uint8Array } { +}: MultibasePublicKey & + Partial): TypedKeypairWithOptionalSecretKey { const { keyBytes, prefix } = decodeBase58BtcMultikey(publicKeyMultibase) const keyType = publicKeyPrefixes[prefix] @@ -153,7 +168,7 @@ export function decodeMultibaseKeypair({ ) } - const result: ReturnType = { + const result: TypedKeypairWithOptionalSecretKey = { type: keyType, publicKey: keyBytes, } From 8b8c5387d8a657a2eedb605608dad13057f9086a Mon Sep 17 00:00:00 2001 From: Raphael Flechtner <39338561+rflechtner@users.noreply.github.com> Date: Wed, 24 Jul 2024 10:08:54 +0200 Subject: [PATCH 67/70] refactor!: align issuer/holder interfaces (#889) * refactor: move did helpers interfaces to types * refactor: make sure issuer/holder functions can work with did helpers * chore: nitpicks * refactor!: move issuer options use them on lower-level issue too * test: fix unit tests --- .../src/blockchain/Blockchain.ts | 14 +- .../src/V1/KiltAttestationProofV1.ts | 209 +++++++++--------- packages/credentials/src/holder.ts | 9 +- packages/credentials/src/interfaces.ts | 35 ++- packages/credentials/src/issuer.ts | 15 +- .../src/suites/KiltAttestationProofV1.spec.ts | 44 ++-- .../src/suites/KiltAttestationProofV1.ts | 13 +- .../src/suites/Sr25519Signature2020.spec.ts | 4 +- packages/sdk-js/src/DidHelpers/checkResult.ts | 9 +- packages/sdk-js/src/DidHelpers/common.ts | 6 +- .../sdk-js/src/DidHelpers/createDid.spec.ts | 5 +- packages/sdk-js/src/DidHelpers/createDid.ts | 15 +- .../sdk-js/src/DidHelpers/deactivateDid.ts | 2 +- .../sdk-js/src/DidHelpers/selectSigner.ts | 8 +- packages/sdk-js/src/DidHelpers/service.ts | 9 +- .../sdk-js/src/DidHelpers/transact.spec.ts | 7 +- packages/sdk-js/src/DidHelpers/transact.ts | 10 +- .../src/DidHelpers/verificationMethod.ts | 10 +- packages/sdk-js/src/DidHelpers/w3names.ts | 2 +- .../interfaces.ts => types/src/DidHelpers.ts} | 28 ++- packages/types/src/Imported.ts | 3 + packages/types/src/Signers.ts | 6 + packages/types/src/index.ts | 1 + tests/bundle/bundle-test.ts | 10 +- tests/integration/didHelpers.spec.ts | 44 +++- tests/integration/utils.ts | 5 +- tests/testUtils/TestUtils.ts | 3 +- 27 files changed, 295 insertions(+), 231 deletions(-) rename packages/{sdk-js/src/DidHelpers/interfaces.ts => types/src/DidHelpers.ts} (87%) diff --git a/packages/chain-helpers/src/blockchain/Blockchain.ts b/packages/chain-helpers/src/blockchain/Blockchain.ts index ea10c1367..ea7f053a0 100644 --- a/packages/chain-helpers/src/blockchain/Blockchain.ts +++ b/packages/chain-helpers/src/blockchain/Blockchain.ts @@ -5,25 +5,22 @@ * found in the LICENSE file in the root directory of this source tree. */ -import type { ApiPromise } from '@polkadot/api' +import { ApiPromise, SubmittableResult } from '@polkadot/api' import type { TxWithEvent } from '@polkadot/api-derive/types' import type { Vec } from '@polkadot/types' import type { Call, Extrinsic } from '@polkadot/types/interfaces' import type { AnyNumber, IMethod } from '@polkadot/types/types' import type { BN } from '@polkadot/util' + // eslint-disable-next-line @typescript-eslint/no-unused-vars -- doing this instead of import '@kiltprotocol/augment-api' to avoid creating an import at runtime import type * as _ from '@kiltprotocol/augment-api' import type { ISubmittableResult, KeyringPair, - KiltAddress, - SignerInterface, SubmittableExtrinsic, SubscriptionPromise, + TransactionSigner, } from '@kiltprotocol/types' - -import { SubmittableResult } from '@polkadot/api' - import { ConfigService } from '@kiltprotocol/config' import { SDKErrors, Signers } from '@kiltprotocol/utils' @@ -170,11 +167,6 @@ export async function submitSignedTx( export const dispatchTx = submitSignedTx -export type TransactionSigner = SignerInterface< - 'Ecrecover-Secp256k1-Blake2b' | 'Sr25519' | 'Ed25519', - KiltAddress -> - /** * Signs a SubmittableExtrinsic. * diff --git a/packages/credentials/src/V1/KiltAttestationProofV1.ts b/packages/credentials/src/V1/KiltAttestationProofV1.ts index 3dc4995c2..9198e7069 100644 --- a/packages/credentials/src/V1/KiltAttestationProofV1.ts +++ b/packages/credentials/src/V1/KiltAttestationProofV1.ts @@ -5,7 +5,17 @@ * found in the LICENSE file in the root directory of this source tree. */ +import type { ApiPromise } from '@polkadot/api' +import type { QueryableStorageEntry } from '@polkadot/api/types' +import type { Option, u64, Vec } from '@polkadot/types' +import type { + AccountId, + Extrinsic, + Hash, +} from '@polkadot/types/interfaces/types.js' +import type { IEventData } from '@polkadot/types/types' import { + hexToU8a, stringToU8a, u8aCmp, u8aConcatStrict, @@ -19,40 +29,37 @@ import { encodeAddress, randomAsU8a, } from '@polkadot/util-crypto' -import type { ApiPromise } from '@polkadot/api' -import type { QueryableStorageEntry } from '@polkadot/api/types' -import type { Option, u64, Vec } from '@polkadot/types' -import type { - AccountId, - EventRecord, - Extrinsic, - Hash, -} from '@polkadot/types/interfaces/types.js' -import type { IEventData } from '@polkadot/types/types' +import type { + FrameSystemEventRecord, + RuntimeCommonAuthorizationAuthorizationId, +} from '@kiltprotocol/augment-api' +import { Blockchain } from '@kiltprotocol/chain-helpers' +import { ConfigService } from '@kiltprotocol/config' import { authorizeTx, + fromChain, getFullDid, + signersForDid, validateDid, - fromChain, } from '@kiltprotocol/did' -import { JsonSchema, SDKErrors, Caip19, Signers } from '@kiltprotocol/utils' -import { ConfigService } from '@kiltprotocol/config' -import { Blockchain } from '@kiltprotocol/chain-helpers' import type { - FrameSystemEventRecord, - RuntimeCommonAuthorizationAuthorizationId, -} from '@kiltprotocol/augment-api' -import type { - DidDocument, Did, ICType, IDelegationNode, KiltAddress, - SignerInterface, + SharedArguments, } from '@kiltprotocol/types' -import * as CType from '../ctype/index.js' +import { Caip19, JsonSchema, SDKErrors, Signers } from '@kiltprotocol/utils' +import { CTypeLoader } from '../ctype/CTypeLoader.js' +import * as CType from '../ctype/index.js' +import { + IssuerOptions, + SimplifiedTransactionResult, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + SubmitOverride, +} from '../interfaces.js' import { DEFAULT_CREDENTIAL_CONTEXTS, validateStructure as validateCredentialStructure, @@ -60,26 +67,25 @@ import { } from './KiltCredentialV1.js' import { fromGenesisAndRootHash } from './KiltRevocationStatusV1.js' import { - jsonLdExpandCredentialSubject, - ExpandedContents, - delegationIdFromAttesterDelegation, - getDelegationNodeIdForCredential, assertMatchingConnection, credentialIdFromRootHash, credentialIdToRootHash, - KILT_CREDENTIAL_IRI_PREFIX, + delegationIdFromAttesterDelegation, + ExpandedContents, + getDelegationNodeIdForCredential, + jsonLdExpandCredentialSubject, KILT_ATTESTER_DELEGATION_V1_TYPE, KILT_ATTESTER_LEGITIMATION_V1_TYPE, + KILT_CREDENTIAL_IRI_PREFIX, spiritnetGenesisHash, } from './common.js' +import { KiltRevocationStatusV1 } from './index.js' import type { CredentialSubject, KiltAttestationProofV1, KiltAttesterLegitimationV1, KiltCredentialV1, } from './types.js' -import { CTypeLoader } from '../ctype/CTypeLoader.js' -import { KiltRevocationStatusV1 } from './index.js' export type Interface = KiltAttestationProofV1 @@ -659,42 +665,41 @@ export function finalizeProof( } } -export interface TransactionResult { - status: 'InBlock' | 'Finalized' - includedAt: { blockHash: Uint8Array; blockHeight?: BigInt; blockTime?: Date } - events?: EventRecord[] -} +async function defaultTxSubmit({ + didDocument, + call, + signers, + submitter, +}: Omit & { + call: Extrinsic + submitter: KiltAddress +}): Promise { + let extrinsic = await authorizeTx( + didDocument, + call, + await signersForDid(didDocument, ...signers), + submitter + ) -type CustomHandlers = { - authorizeTx: (tx: Extrinsic) => Promise - submitTx: (tx: Extrinsic) => Promise -} -type SignersAndSubmitter = { - submitterAccount: KiltAddress - signers: readonly SignerInterface[] -} -export type IssueOpts = - | (CustomHandlers & Partial) - | (Partial & SignersAndSubmitter) - -async function defaultTxSubmit( - tx: Extrinsic, - submitterAccount: KiltAddress, - signers: readonly SignerInterface[], - api: ApiPromise -): Promise { - const extrinsic = api.tx(tx) - const signed = extrinsic.isSigned - ? extrinsic - : await extrinsic.signAsync(submitterAccount, { - signer: Signers.getPolkadotSigner(signers), - }) - const result = await Blockchain.submitSignedTx(signed, { + if (!extrinsic.isSigned) { + const accountSigners = ( + await Promise.all( + signers.map((keypair) => + 'algorithm' in keypair + ? [keypair] + : Signers.getSignersForKeypair({ keypair }) + ) + ) + ).flat() + extrinsic = await extrinsic.signAsync(submitter, { + signer: Signers.getPolkadotSigner(accountSigners), + }) + } + const result = await Blockchain.submitSignedTx(extrinsic, { resolveOn: Blockchain.IS_FINALIZED, }) const blockHash = result.status.asFinalized - const { events } = result - return { status: 'Finalized', includedAt: { blockHash }, events } + return { block: { hash: blockHash.toHex() } } } /** @@ -702,71 +707,57 @@ async function defaultTxSubmit( * Creates a complete {@link KiltAttestationProofV1} for issuing a new credential. * * @param credential A {@link KiltCredentialV1} for which a proof shall be created. - * @param issuer The DID or DID Document of the DID acting as the issuer. - * @param options Additional parameters. - * @param options.signers An array of signer interfaces related to the issuer's keys. The function selects the appropriate handlers for all signatures required for issuance (e.g., authorizing the on-chain anchoring of the credential). - * This can be omitted if both a custom authorizeTx & submitTx are given. - * @param options.submitterAccount The account which counter-signs the transaction to cover the transaction fees. - * Can be omitted if both a custom authorizeTx & submitTx are given. - * @param options.authorizeTx Allows overriding the function that takes a transaction and adds authorization by signing it with keys associated with the issuer DID. - * @param options.submitTx Allows overriding the function that takes the DID-signed transaction and submits it to a blockchain node, tracking its inclusion in a block. - * It is expected to at least return the hash of the block at which the transaction was processed. + * @param issuer Parameters describing the credential issuer. + * @param issuer.didDocument The DID Document of the DID acting as the issuer. + * @param issuer.signers An array of signer interfaces related to the issuer's keys. The function selects the appropriate handlers for all signatures required for issuance (e.g., authorizing the on-chain anchoring of the credential). + * @param issuer.submitter Either the account which counter-signs the transaction to cover the transaction fees, + * _OR_ a user-provided implementation did-authorization and submitting logic following the {@link SubmitOverride} interface. * @returns The credential where `id`, `credentialStatus`, and `issuanceDate` have been updated based on the on-chain attestation record, containing a finalized proof. */ export async function issue( credential: Omit, - issuer: Did | DidDocument, - options: IssueOpts + issuer: IssuerOptions ): Promise { + const { didDocument, signers, submitter } = issuer const updatedCredential = { ...credential, - issuer: typeof issuer === 'string' ? issuer : issuer.id, + issuer: didDocument.id, } const [proof, callArgs] = initializeProof(updatedCredential) const api = ConfigService.get('api') const call = api.tx.attestation.add(...callArgs) - const { + const args: Pick & { + call: Extrinsic + } = { + didDocument, signers, - submitterAccount, - authorizeTx: customAuthorizeTx, - submitTx: customSubmitTx, - ...otherParams - } = options - - if ( - !(customAuthorizeTx && customSubmitTx) && - !(signers && submitterAccount) - ) { - throw new Error( - '`signers` and `submitterAccount` are required options if authorizeTx or submitTx are not given' - ) + api, + call, } - - /* eslint-disable @typescript-eslint/no-non-null-assertion -- we've checked the appropriate combination of parameters above, but typescript does not follow */ - const didSigned = customAuthorizeTx - ? await customAuthorizeTx(call) - : await authorizeTx(issuer, call, signers!, submitterAccount!, otherParams) - - const transactionPromise = customSubmitTx - ? customSubmitTx(didSigned) - : defaultTxSubmit(didSigned, submitterAccount!, signers!, api) - /* eslint-enable @typescript-eslint/no-non-null-assertion */ - - const { - status, - includedAt: { blockHash, blockTime }, - } = await transactionPromise - - if (status !== 'Finalized' && status !== 'InBlock') { - throw new SDKErrors.SDKError( - `Unexpected transaction status ${status}; the transaction should be "InBlock" or "Finalized" for issuance to continue` - ) + const transactionPromise = + typeof submitter === 'function' + ? submitter(args) + : defaultTxSubmit({ + ...args, + submitter, + }) + + let result = await transactionPromise + if ('status' in result) { + if (result.status !== 'confirmed') { + throw new SDKErrors.SDKError( + `Unexpected transaction status ${result.status}; the transaction should be "confirmed" for issuance to continue` + ) + } + result = result.asConfirmed } - const timestamp = - blockTime ?? - new Date((await api.query.timestamp.now.at(blockHash)).toNumber()) + const blockHash = hexToU8a(result.block.hash) + + const timestamp = new Date( + (await (await api.at(blockHash)).query.timestamp.now()).toNumber() + ) return finalizeProof(updatedCredential, proof, { blockHash, timestamp, diff --git a/packages/credentials/src/holder.ts b/packages/credentials/src/holder.ts index ef981f433..73cdf026e 100644 --- a/packages/credentials/src/holder.ts +++ b/packages/credentials/src/holder.ts @@ -10,6 +10,7 @@ import type { CryptoSuite } from '@kiltprotocol/jcs-data-integrity-proofs-common import { Did } from '@kiltprotocol/types' import { SDKErrors } from '@kiltprotocol/utils' +import { signersForDid } from '@kiltprotocol/did' import { KiltAttestationProofV1, KiltCredentialV1 } from './V1/index.js' import { VerifiableCredential, VerifiablePresentation } from './V1/types.js' @@ -159,13 +160,13 @@ export async function createPresentation( now?: Date } = {} ): Promise { - const { did, didDocument, signers } = holder + const { didDocument, signers } = holder const { validFrom, validUntil, verifier } = presentationOptions const { proofPurpose, proofType, challenge, domain, now } = proofOptions let presentation = await Presentation.create({ credentials, - holder: did, + holder: didDocument.id, validFrom: validFrom ?? now, validUntil, verifier, @@ -184,8 +185,8 @@ export async function createPresentation( presentation = await DataIntegrity.signWithDid({ document: presentation, - signerDid: didDocument ?? did, - signers, + signerDid: didDocument, + signers: await signersForDid(didDocument, ...signers), proofPurpose, challenge, domain, diff --git a/packages/credentials/src/interfaces.ts b/packages/credentials/src/interfaces.ts index b2033729a..9494aec61 100644 --- a/packages/credentials/src/interfaces.ts +++ b/packages/credentials/src/interfaces.ts @@ -5,20 +5,41 @@ * found in the LICENSE file in the root directory of this source tree. */ -import type { SignerInterface } from '@kiltprotocol/jcs-data-integrity-proofs-common' -import type { Did, DidDocument } from '@kiltprotocol/types' +import type { Extrinsic } from '@polkadot/types/interfaces' + +import type { + DidDocument, + DidHelpersAcceptedSigners, + HexString, + KiltAddress, + SharedArguments, + TransactionResult, +} from '@kiltprotocol/types' + import type { Proof, VerifiableCredential } from './V1/types' -import type { IssueOpts } from './V1/KiltAttestationProofV1' export type SecuredDocument = { proof: Proof[] | Proof } export interface HolderOptions { - did: Did - didDocument?: DidDocument - signers: SignerInterface[] + didDocument: DidDocument + signers: DidHelpersAcceptedSigners[] +} + +export interface SimplifiedTransactionResult { + block: { hash: HexString } +} + +export type SubmitOverride = ( + args: Pick & { + call: Extrinsic + } +) => Promise + +interface SubmitterAddressOrOverride { + submitter: KiltAddress | SubmitOverride } -export type IssuerOptions = HolderOptions & IssueOpts +export type IssuerOptions = HolderOptions & SubmitterAddressOrOverride export interface VerificationResult { verified: boolean diff --git a/packages/credentials/src/issuer.ts b/packages/credentials/src/issuer.ts index 02df23d72..e1107cfd5 100644 --- a/packages/credentials/src/issuer.ts +++ b/packages/credentials/src/issuer.ts @@ -13,15 +13,14 @@ import type { UnsignedVc, VerifiableCredential } from './V1/types.js' import type { CTypeLoader } from './ctype/index.js' import type { IssuerOptions } from './interfaces.js' +export type { IssuerOptions } + /** * Creates a new credential document as a basis for issuing a credential. * This document can be shown to users as a preview or be extended with additional properties before moving on to the second step of credential issuance: * Adding a `proof` to the document using the {@link issue} function to make the credential verifiable. * * @param arguments Object holding all arguments for credential creation. - * @param arguments.type A type string identifying the (sub-)type of Verifiable Credential to be created. - * This is added to the `type` field on the credential and determines the `credentialSchema` as well. - * Defaults to the type {@link KiltCredentialV1.CREDENTIAL_TYPE KiltCredentialV1} which, for the time being, is also the only type supported. * @param arguments.issuer The Decentralized Identifier (DID) of the identity acting as the authority issuing this credential. * @param arguments.credentialSubject An object containing key-value pairs that represent claims made about the subject of the credential. * @param arguments.credentialSubject.id The DID identifying the subject of the credential, about which claims are made (the remaining key-value pairs). @@ -29,20 +28,23 @@ import type { IssuerOptions } from './interfaces.js' * Each Kilt credential is based on exactly one of these subtypes. This argument is therefore mandatory and expects the schema definition of a CType. * @param arguments.cTypeDefinitions Some CTypes are themselves composed of definitions taken from other CTypes; in that case, these definitions need to be supplied here. * Alternatively, you can set a {@link CTypeLoader} function that takes care of fetching all required definitions. + * @param arguments.type A type string identifying the (sub-)type of Verifiable Credential to be created. + * This is added to the `type` field on the credential and determines the `credentialSchema` as well. + * Defaults to the type {@link KiltCredentialV1.CREDENTIAL_TYPE KiltCredentialV1} which, for the time being, is also the only type supported. * @returns A (potentially only partial) credential that is yet to be finalized and made verifiable with a proof. */ export async function createCredential({ - type, issuer, credentialSubject, cType, cTypeDefinitions, + type, }: { - type?: string issuer: Did credentialSubject: Record & { id: Did } cType: ICType cTypeDefinitions?: ICType[] | CTypeLoader + type?: string }): Promise { switch (type) { case undefined: @@ -120,11 +122,8 @@ export async function issue( switch (proofType) { case undefined: case KiltAttestationProofV1.PROOF_TYPE: { - const { didDocument, did } = issuer - const cred = await KiltAttestationProofV1.issue( credential as KiltCredentialV1.Interface, - didDocument ?? did, issuer ) diff --git a/packages/jsonld-suites/src/suites/KiltAttestationProofV1.spec.ts b/packages/jsonld-suites/src/suites/KiltAttestationProofV1.spec.ts index fff6928ef..35adb0514 100644 --- a/packages/jsonld-suites/src/suites/KiltAttestationProofV1.spec.ts +++ b/packages/jsonld-suites/src/suites/KiltAttestationProofV1.spec.ts @@ -5,7 +5,7 @@ * found in the LICENSE file in the root directory of this source tree. */ -import { u8aEq } from '@polkadot/util' +import { u8aEq, u8aToHex } from '@polkadot/util' import { base58Decode } from '@polkadot/util-crypto' import { Ed25519Signature2020, @@ -36,6 +36,7 @@ import { KiltCredentialV1, Types, W3C_CREDENTIAL_CONTEXT_URL, + type Issuer, } from '@kiltprotocol/credentials' import { @@ -448,28 +449,23 @@ describe('issuance', () => { let toBeSigned: CredentialStub const { issuer } = attestedVc - const signer: SignerInterface<'Sr25519', DidUrl> = { - sign: async () => new Uint8Array(32), - algorithm: 'Sr25519', - id: `${issuer}#1`, - } - const transactionHandler: KiltAttestationProofV1.IssueOpts = { - signers: [signer], - submitterAccount: attester, - submitTx: async () => { - return { - status: 'Finalized', - includedAt: { - blockHash, - blockTime: timestamp, - }, - } - }, - authorizeTx: async (tx) => { - txArgs = tx.args - return tx + const signers: Array> = [ + { + sign: async () => new Uint8Array(32), + algorithm: 'Sr25519', + id: `${issuer}#1`, }, + ] + + const submitter: Issuer.IssuerOptions['submitter'] = async ({ call }) => { + txArgs = call.args + return { + block: { + hash: u8aToHex(blockHash), + }, + } } + beforeEach(() => { toBeSigned = { credentialSubject: attestedVc.credentialSubject, @@ -481,8 +477,7 @@ describe('issuance', () => { let newCred: Partial = await issuanceSuite.anchorCredential( { ...toBeSigned }, - issuer, - transactionHandler + { didDocument, signers, submitter } ) newCred = await vcjs.issue({ credential: newCred, @@ -540,8 +535,7 @@ describe('issuance', () => { { ...toBeSigned, }, - issuer, - transactionHandler + { didDocument, signers, submitter } ) newCred = (await vcjs.issue({ credential: { diff --git a/packages/jsonld-suites/src/suites/KiltAttestationProofV1.ts b/packages/jsonld-suites/src/suites/KiltAttestationProofV1.ts index 7b6366dc6..783fbc4f5 100644 --- a/packages/jsonld-suites/src/suites/KiltAttestationProofV1.ts +++ b/packages/jsonld-suites/src/suites/KiltAttestationProofV1.ts @@ -15,11 +15,11 @@ import { ConfigService } from '@kiltprotocol/config' import { CType, KiltAttestationProofV1, + KiltCredentialV1, KiltRevocationStatusV1, Types, - KiltCredentialV1, } from '@kiltprotocol/credentials' -import type { DidDocument, Did, ICType } from '@kiltprotocol/types' +import type { ICType } from '@kiltprotocol/types' import { Caip2 } from '@kiltprotocol/utils' import type { DocumentLoader, JsonLdObj } from '../documentLoader.js' @@ -201,15 +201,13 @@ export class KiltAttestationV1Suite extends LinkedDataProof { * You can then add a proof about the successful attestation to the credential using `createProof`. * * @param input A partial {@link KiltCredentialV1} `credentialSubject` is required. - * @param issuer The DID Document or, alternatively, the DID of the issuer. - * @param submissionOptions Authorization and submission handlers, or alternatively signers, to be passed to {@link KiltAttestationProofV1.issue | issue} for authorizing the on-chain anchoring of the credential with the issuer's signature. + * @param issuer Parameters describing the credential issuer, such as the issuer's `didDocument` and `signers`, to be passed to {@link KiltAttestationProofV1.issue | issue}. * * @returns A copy of the input updated to fit the {@link KiltCredentialV1} and to align with the attestation record (concerns, e.g., the `issuanceDate` which is set to the block time at which the credential was anchored). */ public async anchorCredential( input: CredentialStub, - issuer: DidDocument | Did, - submissionOptions: Parameters['2'] + issuer: Parameters['1'] ): Promise> { const { credentialSubject, type } = input @@ -239,8 +237,7 @@ export class KiltAttestationV1Suite extends LinkedDataProof { const { proof, ...credential } = await KiltAttestationProofV1.issue( credentialStub, - issuer, - submissionOptions + issuer ) this.attestationInfo.set(credential.id, proof) diff --git a/packages/jsonld-suites/src/suites/Sr25519Signature2020.spec.ts b/packages/jsonld-suites/src/suites/Sr25519Signature2020.spec.ts index 8270f2b33..3ad8f540c 100644 --- a/packages/jsonld-suites/src/suites/Sr25519Signature2020.spec.ts +++ b/packages/jsonld-suites/src/suites/Sr25519Signature2020.spec.ts @@ -43,7 +43,7 @@ const documentLoader = combineDocumentLoaders([ export async function makeFakeDid() { const keypair = Crypto.makeKeypairFromUri('//Ingo', 'sr25519') - const id = ingosCredential.credentialSubject.id as KiltDid + const id = ingosCredential.issuer as KiltDid const didDocument: DidDocument = { id, authentication: [`${id}#authentication`], @@ -101,7 +101,7 @@ it('issues and verifies a signed credential', async () => { '@context': [W3C_CREDENTIAL_CONTEXT_URL] as any, type: ['VerifiableCredential'], credentialSubject: ingosCredential.credentialSubject, - issuer: ingosCredential.credentialSubject.id, + issuer: ingosCredential.issuer, } as Partial const verifiableCredential = await vcjs.issue({ diff --git a/packages/sdk-js/src/DidHelpers/checkResult.ts b/packages/sdk-js/src/DidHelpers/checkResult.ts index cb8e545bc..47fe0e5b3 100644 --- a/packages/sdk-js/src/DidHelpers/checkResult.ts +++ b/packages/sdk-js/src/DidHelpers/checkResult.ts @@ -12,13 +12,18 @@ import type { ApiPromise } from '@polkadot/api' import type { SubmittableResultValue } from '@polkadot/api/types' import type { BlockNumber } from '@polkadot/types/interfaces' import { u8aToHex, u8aToU8a } from '@polkadot/util' + import type { FrameSystemEventRecord as EventRecord, SpRuntimeDispatchError, } from '@kiltprotocol/augment-api' import { resolver as DidResolver, signersForDid } from '@kiltprotocol/did' -import type { Did, HexString } from '@kiltprotocol/types' -import type { SharedArguments, TransactionResult } from './interfaces.js' +import type { + Did, + HexString, + SharedArguments, + TransactionResult, +} from '@kiltprotocol/types' function mapError(err: SpRuntimeDispatchError, api: ApiPromise): Error { if (err.isModule) { diff --git a/packages/sdk-js/src/DidHelpers/common.ts b/packages/sdk-js/src/DidHelpers/common.ts index bcf2ceced..653a4dc46 100644 --- a/packages/sdk-js/src/DidHelpers/common.ts +++ b/packages/sdk-js/src/DidHelpers/common.ts @@ -12,10 +12,10 @@ import { Blockchain } from '@kiltprotocol/chain-helpers' import { multibaseKeyToDidKey } from '@kiltprotocol/did' import type { - AcceptedPublicKeyEncodings, + DidHelpersAcceptedPublicKeyEncodings, SharedArguments, TransactionHandlers, -} from './interfaces.js' +} from '@kiltprotocol/types' export async function submitImpl( getSubmittable: TransactionHandlers['getSubmittable'], @@ -40,7 +40,7 @@ export async function submitImpl( return submittable.checkResult(result) } -export function convertPublicKey(pk: AcceptedPublicKeyEncodings): { +export function convertPublicKey(pk: DidHelpersAcceptedPublicKeyEncodings): { publicKey: Uint8Array type: string } { diff --git a/packages/sdk-js/src/DidHelpers/createDid.spec.ts b/packages/sdk-js/src/DidHelpers/createDid.spec.ts index 3949d1e75..9055b9e32 100644 --- a/packages/sdk-js/src/DidHelpers/createDid.spec.ts +++ b/packages/sdk-js/src/DidHelpers/createDid.spec.ts @@ -5,17 +5,16 @@ * found in the LICENSE file in the root directory of this source tree. */ -import type { KiltKeyringPair } from '@kiltprotocol/types' +import { resolver } from '@kiltprotocol/did' +import type { KiltKeyringPair, TransactionResult } from '@kiltprotocol/types' import { Crypto } from '@kiltprotocol/utils' import { SubmittableResult } from '@polkadot/api' -import { resolver } from '@kiltprotocol/did' import { ApiMocks, createLocalDemoFullDidFromKeypair, } from '../../../../tests/testUtils/index.js' import { ConfigService } from '../index.js' import { createDid } from './createDid.js' -import { TransactionResult } from './interfaces.js' const mockedApi = ApiMocks.createAugmentedApi() jest.mock('@kiltprotocol/did', () => { diff --git a/packages/sdk-js/src/DidHelpers/createDid.ts b/packages/sdk-js/src/DidHelpers/createDid.ts index 285fd5472..c5f63d76d 100644 --- a/packages/sdk-js/src/DidHelpers/createDid.ts +++ b/packages/sdk-js/src/DidHelpers/createDid.ts @@ -12,16 +12,17 @@ import { type NewDidVerificationKey, signingMethodTypes, } from '@kiltprotocol/did' -import type { KiltAddress, SignerInterface } from '@kiltprotocol/types' +import type { + DidHelpersAcceptedPublicKeyEncodings, + KiltAddress, + SharedArguments, + SignerInterface, + TransactionHandlers, +} from '@kiltprotocol/types' import { Crypto, Signers } from '@kiltprotocol/utils' import { checkResultImpl } from './checkResult.js' import { convertPublicKey, submitImpl } from './common.js' -import type { - AcceptedPublicKeyEncodings, - SharedArguments, - TransactionHandlers, -} from './interfaces.js' function implementsSignerInterface(input: any): input is SignerInterface { return 'algorithm' in input && 'id' in input && 'sign' in input @@ -36,7 +37,7 @@ function implementsSignerInterface(input: any): input is SignerInterface { */ export function createDid( options: Omit & { - fromPublicKey: AcceptedPublicKeyEncodings + fromPublicKey: DidHelpersAcceptedPublicKeyEncodings } ): TransactionHandlers { const getSubmittable: TransactionHandlers['getSubmittable'] = async ( diff --git a/packages/sdk-js/src/DidHelpers/deactivateDid.ts b/packages/sdk-js/src/DidHelpers/deactivateDid.ts index 1b684059f..a35fb0546 100644 --- a/packages/sdk-js/src/DidHelpers/deactivateDid.ts +++ b/packages/sdk-js/src/DidHelpers/deactivateDid.ts @@ -5,7 +5,7 @@ * found in the LICENSE file in the root directory of this source tree. */ -import type { SharedArguments, TransactionHandlers } from './interfaces.js' +import type { SharedArguments, TransactionHandlers } from '@kiltprotocol/types' import { transactInternal } from './transact.js' /** diff --git a/packages/sdk-js/src/DidHelpers/selectSigner.ts b/packages/sdk-js/src/DidHelpers/selectSigner.ts index 7f83b1ff6..2cf7efe44 100644 --- a/packages/sdk-js/src/DidHelpers/selectSigner.ts +++ b/packages/sdk-js/src/DidHelpers/selectSigner.ts @@ -6,11 +6,13 @@ */ import { signersForDid } from '@kiltprotocol/did' -import type { DidUrl, SignerInterface } from '@kiltprotocol/types' +import type { + DidUrl, + SignerInterface, + SharedArguments, +} 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. * diff --git a/packages/sdk-js/src/DidHelpers/service.ts b/packages/sdk-js/src/DidHelpers/service.ts index f6e346a26..95b07563e 100644 --- a/packages/sdk-js/src/DidHelpers/service.ts +++ b/packages/sdk-js/src/DidHelpers/service.ts @@ -6,8 +6,13 @@ */ import { serviceToChain, urlFragmentToChain } from '@kiltprotocol/did' -import type { DidUrl, Service, UriFragment } from '@kiltprotocol/types' -import { SharedArguments, TransactionHandlers } from './interfaces.js' +import type { + DidUrl, + Service, + SharedArguments, + TransactionHandlers, + UriFragment, +} from '@kiltprotocol/types' import { transactInternal } from './transact.js' /** diff --git a/packages/sdk-js/src/DidHelpers/transact.spec.ts b/packages/sdk-js/src/DidHelpers/transact.spec.ts index 3101a6fcc..1c4541e8f 100644 --- a/packages/sdk-js/src/DidHelpers/transact.spec.ts +++ b/packages/sdk-js/src/DidHelpers/transact.spec.ts @@ -6,7 +6,11 @@ */ import { authorizeTx, resolver } from '@kiltprotocol/did' -import type { DidDocument, KiltKeyringPair } from '@kiltprotocol/types' +import type { + DidDocument, + KiltKeyringPair, + TransactionResult, +} from '@kiltprotocol/types' import { Crypto } from '@kiltprotocol/utils' import { SubmittableResult } from '@polkadot/api' @@ -16,7 +20,6 @@ import { } from '../../../../tests/testUtils/index.js' import { makeAttestationCreatedEvents } from '../../../../tests/testUtils/testData.js' import { ConfigService } from '../index.js' -import { TransactionResult } from './interfaces.js' import { transact } from './transact.js' jest.mock('@kiltprotocol/did', () => { diff --git a/packages/sdk-js/src/DidHelpers/transact.ts b/packages/sdk-js/src/DidHelpers/transact.ts index 393f32bf7..ad0474043 100644 --- a/packages/sdk-js/src/DidHelpers/transact.ts +++ b/packages/sdk-js/src/DidHelpers/transact.ts @@ -9,11 +9,15 @@ 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 type { + KiltAddress, + SharedArguments, + SubmittableExtrinsic, + TransactionHandlers, +} from '@kiltprotocol/types' -import { submitImpl } from './common.js' -import type { SharedArguments, TransactionHandlers } from './interfaces.js' import { checkResultImpl } from './checkResult.js' +import { submitImpl } from './common.js' /** * Instructs a transaction (state transition) as this DID (with this DID as the origin). diff --git a/packages/sdk-js/src/DidHelpers/verificationMethod.ts b/packages/sdk-js/src/DidHelpers/verificationMethod.ts index 252d52a1b..686c399b7 100644 --- a/packages/sdk-js/src/DidHelpers/verificationMethod.ts +++ b/packages/sdk-js/src/DidHelpers/verificationMethod.ts @@ -15,14 +15,12 @@ import type { DidUrl, SubmittableExtrinsic, VerificationRelationship, + DidHelpersAcceptedPublicKeyEncodings, + SharedArguments, + TransactionHandlers, } from '@kiltprotocol/types' import { convertPublicKey } from './common.js' -import type { - AcceptedPublicKeyEncodings, - SharedArguments, - TransactionHandlers, -} from './interfaces.js' import { transactInternal } from './transact.js' /** @@ -36,7 +34,7 @@ import { transactInternal } from './transact.js' */ export function setVerificationMethod( options: SharedArguments & { - publicKey: AcceptedPublicKeyEncodings + publicKey: DidHelpersAcceptedPublicKeyEncodings relationship: VerificationRelationship } ): TransactionHandlers { diff --git a/packages/sdk-js/src/DidHelpers/w3names.ts b/packages/sdk-js/src/DidHelpers/w3names.ts index 5feeed056..fee5a416c 100644 --- a/packages/sdk-js/src/DidHelpers/w3names.ts +++ b/packages/sdk-js/src/DidHelpers/w3names.ts @@ -5,7 +5,7 @@ * found in the LICENSE file in the root directory of this source tree. */ -import type { SharedArguments, TransactionHandlers } from './interfaces.js' +import type { SharedArguments, TransactionHandlers } from '@kiltprotocol/types' import { transactInternal } from './transact.js' /** diff --git a/packages/sdk-js/src/DidHelpers/interfaces.ts b/packages/types/src/DidHelpers.ts similarity index 87% rename from packages/sdk-js/src/DidHelpers/interfaces.ts rename to packages/types/src/DidHelpers.ts index 8fd4dbfbf..2ea7f82de 100644 --- a/packages/sdk-js/src/DidHelpers/interfaces.ts +++ b/packages/types/src/DidHelpers.ts @@ -5,20 +5,21 @@ * found in the LICENSE file in the root directory of this source tree. */ -import type { ApiPromise } from '@polkadot/api' -import type { SubmittableResultValue } from '@polkadot/api/types' -import type { GenericEvent } from '@polkadot/types' - -import type { Blockchain } from '@kiltprotocol/chain-helpers' +import type { Multikey } from '@kiltprotocol/utils' +import type { DidDocument } from './Did' import type { - DidDocument, + ApiPromise, + GenericEvent, HexString, KeyringPair, + SubmittableResultValue, +} from './Imported' +import type { MultibaseKeyPair, MultibasePublicKey, SignerInterface, -} from '@kiltprotocol/types' -import type { Multikey } from '@kiltprotocol/utils' + TransactionSigner, +} from './Signers' export interface TransactionResult { status: 'confirmed' | 'failed' | 'rejected' | 'unknown' @@ -95,13 +96,16 @@ export interface TransactionHandlers { }> } -export type AcceptedSigners = SignerInterface | KeyringPair | MultibaseKeyPair +export type DidHelpersAcceptedSigners = + | SignerInterface + | KeyringPair + | MultibaseKeyPair export type SharedArguments = { didDocument: DidDocument api: ApiPromise - signers: AcceptedSigners[] - submitter: KeyringPair | Blockchain.TransactionSigner + signers: DidHelpersAcceptedSigners[] + submitter: KeyringPair | TransactionSigner } type PublicKeyAndType = { @@ -109,7 +113,7 @@ type PublicKeyAndType = { type: Multikey.KnownTypeString } -export type AcceptedPublicKeyEncodings = +export type DidHelpersAcceptedPublicKeyEncodings = | MultibasePublicKey['publicKeyMultibase'] | MultibasePublicKey | PublicKeyAndType // interface allows KeyringPair too diff --git a/packages/types/src/Imported.ts b/packages/types/src/Imported.ts index 1d46e3095..012d72d3e 100644 --- a/packages/types/src/Imported.ts +++ b/packages/types/src/Imported.ts @@ -15,3 +15,6 @@ export type { HexString } from '@polkadot/util/types' export type { Prefix } from '@polkadot/util-crypto/address/types' export type { SubmittableExtrinsic } from '@polkadot/api/promise/types' export type { KeyringPair } from '@polkadot/keyring/types' +export type { ApiPromise } from '@polkadot/api' +export type { SubmittableResultValue } from '@polkadot/api/types' +export type { GenericEvent } from '@polkadot/types' diff --git a/packages/types/src/Signers.ts b/packages/types/src/Signers.ts index 66fc06ef6..581824b1a 100644 --- a/packages/types/src/Signers.ts +++ b/packages/types/src/Signers.ts @@ -5,6 +5,7 @@ * found in the LICENSE file in the root directory of this source tree. */ +import type { KiltAddress } from './Address' import type { Base58BtcMultibaseString, VerificationMethod } from './Did.js' export type SignerInterface< @@ -16,6 +17,11 @@ export type SignerInterface< sign: (input: { data: Uint8Array }) => Promise } +export type TransactionSigner = SignerInterface< + 'Ecrecover-Secp256k1-Blake2b' | 'Sr25519' | 'Ed25519', + KiltAddress +> + export type MultibasePublicKey = Pick export type MultibaseSecretKey = { secretKeyMultibase: Base58BtcMultibaseString diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 446592fa1..190b35f81 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -26,3 +26,4 @@ export * from './DidResolver.js' export * from './PublicCredential.js' export * from './Imported.js' export * from './Signers.js' +export * from './DidHelpers.js' diff --git a/tests/bundle/bundle-test.ts b/tests/bundle/bundle-test.ts index 7ecca615c..2c3370075 100644 --- a/tests/bundle/bundle-test.ts +++ b/tests/bundle/bundle-test.ts @@ -281,9 +281,9 @@ async function runAll() { } const issued = await Issuer.issue(credential, { - did: alice.id, - signers: [...(await aliceSign(alice)), payerSigner], - submitterAccount: payerSigner.id, + didDocument: alice, + signers: [...aliceSign(alice), payerSigner], + submitter: payerSigner.id, }) console.info('Credential issued') @@ -312,8 +312,8 @@ async function runAll() { const presentation = await Holder.createPresentation( [derived], { - did: bob.id, - signers: await bobSign(bob), + didDocument: bob, + signers: bobSign(bob), }, {}, { diff --git a/tests/integration/didHelpers.spec.ts b/tests/integration/didHelpers.spec.ts index df91f491d..1d7936b99 100644 --- a/tests/integration/didHelpers.spec.ts +++ b/tests/integration/didHelpers.spec.ts @@ -9,7 +9,13 @@ import type { ApiPromise } from '@polkadot/api' import { CType } from '@kiltprotocol/credentials' import { getFullDidFromVerificationMethod } from '@kiltprotocol/did' -import { DidHelpers, disconnect, generateKeypair } from '@kiltprotocol/sdk-js' +import { + DidHelpers, + disconnect, + generateKeypair, + Issuer, + Verifier, +} from '@kiltprotocol/sdk-js' import type { DidDocument, KeyringPair, @@ -355,9 +361,12 @@ describe('transact', () => { ).asConfirmed.didDocument }) + const cType = CType.fromProperties('thing', { + someProperty: { type: 'string' }, + }) + it('creates a ctype', async () => { - const ctype = CType.fromProperties('thing', { thang: { type: 'string' } }) - const serialized = CType.toChain(ctype) + const serialized = CType.toChain(cType) const call = api.tx.ctype.add(serialized) const result = await DidHelpers.transact({ @@ -371,7 +380,34 @@ describe('transact', () => { expect(result.status).toStrictEqual('confirmed') expect(result.asConfirmed.didDocument).toMatchObject(didDocument) - await expect(CType.verifyStored(ctype)).resolves.not.toThrow() + await expect(CType.verifyStored(cType)).resolves.not.toThrow() + }, 30_000) + + it('integrates with issue', async () => { + const unsigned = await Issuer.createCredential({ + issuer: didDocument.id, + credentialSubject: { id: didDocument.id, someProperty: 'someValue' }, + cType, + }) + + const issued = await Issuer.issue(unsigned, { + didDocument, + signers: [keypair], + submitter: async (args) => + DidHelpers.transact({ + ...args, + submitter: paymentAccount, + expectedEvents: [ + { section: 'attestation', method: 'AttestationCreated' }, + ], + }).submit(), + }) + + expect(issued).toHaveProperty('proof', expect.any(Object)) + + await expect( + Verifier.verifyCredential({ credential: issued }) + ).resolves.toMatchObject({ verified: true }) }, 30_000) }) diff --git a/tests/integration/utils.ts b/tests/integration/utils.ts index 6a20a9ffc..c718a7e0d 100644 --- a/tests/integration/utils.ts +++ b/tests/integration/utils.ts @@ -24,6 +24,7 @@ import type { KiltKeyringPair, SubmittableExtrinsic, SubscriptionPromise, + TransactionSigner, } from '@kiltprotocol/types' import { Crypto } from '@kiltprotocol/utils' @@ -155,7 +156,7 @@ export const nftNameCType = CType.fromProperties('NFT collection name', { // Submits resolving when IS_IN_BLOCK export async function submitTx( extrinsic: SubmittableExtrinsic, - submitter: KeyringPair | Blockchain.TransactionSigner, + submitter: KeyringPair | TransactionSigner, resolveOn?: SubscriptionPromise.ResultEvaluator ): Promise { await Blockchain.signAndSubmitTx(extrinsic, submitter, { @@ -164,7 +165,7 @@ export async function submitTx( } export async function endowAccounts( - faucet: KeyringPair | Blockchain.TransactionSigner, + faucet: KeyringPair | TransactionSigner, addresses: string[], resolveOn?: SubscriptionPromise.ResultEvaluator ): Promise { diff --git a/tests/testUtils/TestUtils.ts b/tests/testUtils/TestUtils.ts index 7acae55d7..ad5212a77 100644 --- a/tests/testUtils/TestUtils.ts +++ b/tests/testUtils/TestUtils.ts @@ -16,6 +16,7 @@ import type { KiltKeyringPair, SignerInterface, SubmittableExtrinsic, + TransactionSigner, UriFragment, VerificationMethod, VerificationRelationship, @@ -421,7 +422,7 @@ export async function getStoreTxFromDidDocument( // It takes the auth key from the light DID and use it as attestation and delegation key as well. export async function createFullDidFromLightDid( - payer: KiltKeyringPair | Blockchain.TransactionSigner, + payer: KiltKeyringPair | TransactionSigner, lightDidForId: DidDocument, signer: StoreDidCallback ): Promise { From 51e16da9030ef7e9bceaeb9852a7d4e5723d1165 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab <31316147+abdulmth@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:15:38 +0200 Subject: [PATCH 68/70] fix: unsigned submittable (#890) * fix: unsigned submittable * fix: import * fix: tests * fix: merge issues * fix: addressFromRandom * chore: move test to integration * fix: lint * chore: use return in extractSubmitter --------- Co-authored-by: Raphael Flechtner --- packages/sdk-js/src/DidHelpers/common.ts | 60 ++++++++++++++++++- packages/sdk-js/src/DidHelpers/createDid.ts | 19 +++--- .../sdk-js/src/DidHelpers/transact.spec.ts | 24 +++++++- packages/sdk-js/src/DidHelpers/transact.ts | 21 ++++--- packages/types/src/Address.ts | 8 +-- packages/types/src/DidHelpers.ts | 16 +++-- tests/integration/didHelpers.spec.ts | 19 ++++++ tests/integration/utils.ts | 2 +- 8 files changed, 134 insertions(+), 35 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/common.ts b/packages/sdk-js/src/DidHelpers/common.ts index 653a4dc46..c32eb2557 100644 --- a/packages/sdk-js/src/DidHelpers/common.ts +++ b/packages/sdk-js/src/DidHelpers/common.ts @@ -10,12 +10,16 @@ import { Blockchain } from '@kiltprotocol/chain-helpers' import { multibaseKeyToDidKey } from '@kiltprotocol/did' - import type { + KeyringPair, + KiltAddress, + MultibaseKeyPair, DidHelpersAcceptedPublicKeyEncodings, SharedArguments, TransactionHandlers, + TransactionSigner, } from '@kiltprotocol/types' +import { Keyring, Multikey, Crypto } from '@kiltprotocol/utils' export async function submitImpl( getSubmittable: TransactionHandlers['getSubmittable'], @@ -24,7 +28,10 @@ export async function submitImpl( awaitFinalized?: boolean } ): ReturnType { - const submittable = await getSubmittable(options) + const submittable = await getSubmittable({ + ...options, + signSubmittable: true, + }) const { awaitFinalized = true } = options const result = await Blockchain.submitSignedTx( @@ -63,3 +70,52 @@ export function convertPublicKey(pk: DidHelpersAcceptedPublicKeyEncodings): { } return { publicKey, type } } + +export function extractSubmitterSignerAndAccount( + submitter: KeyringPair | TransactionSigner | MultibaseKeyPair | KiltAddress +): { + submitterSigner?: TransactionSigner | KeyringPair | undefined + submitterAccount: KiltAddress +} { + // KiltAddress + if (typeof submitter === 'string') { + return { + submitterAccount: submitter, + } + } + // KeyringPair + if ('address' in submitter) { + return { + submitterAccount: submitter.address as KiltAddress, + submitterSigner: submitter, + } + } + // Blockchain.TransactionSigner + if ('id' in submitter) { + return { + submitterAccount: submitter.id as KiltAddress, + submitterSigner: submitter, + } + } + // MultibaseKeyPair + if ('publicKeyMultibase' in submitter) { + const submitterAccount = Crypto.encodeAddress( + Multikey.decodeMultibaseKeypair(submitter).publicKey, + 38 + ) + const keypair = Multikey.decodeMultibaseKeypair(submitter) + if (keypair.type === 'x25519') { + throw new Error('x25519 keys are not supported') + } + const keyring = new Keyring().createFromPair( + keypair, + undefined, + keypair.type === 'secp256k1' ? 'ecdsa' : keypair.type + ) + return { + submitterAccount, + submitterSigner: keyring, + } + } + throw new Error('type of submitter is invalid') +} diff --git a/packages/sdk-js/src/DidHelpers/createDid.ts b/packages/sdk-js/src/DidHelpers/createDid.ts index c5f63d76d..86f484ca4 100644 --- a/packages/sdk-js/src/DidHelpers/createDid.ts +++ b/packages/sdk-js/src/DidHelpers/createDid.ts @@ -14,7 +14,6 @@ import { } from '@kiltprotocol/did' import type { DidHelpersAcceptedPublicKeyEncodings, - KiltAddress, SharedArguments, SignerInterface, TransactionHandlers, @@ -22,7 +21,11 @@ import type { import { Crypto, Signers } from '@kiltprotocol/utils' import { checkResultImpl } from './checkResult.js' -import { convertPublicKey, submitImpl } from './common.js' +import { + convertPublicKey, + extractSubmitterSignerAndAccount, + submitImpl, +} from './common.js' function implementsSignerInterface(input: any): input is SignerInterface { return 'algorithm' in input && 'id' in input && 'sign' in input @@ -44,15 +47,14 @@ export function createDid( submitOptions = {} ) => { const { fromPublicKey, submitter, signers, api } = options - const { signSubmittable = true } = submitOptions + const { signSubmittable = false } = submitOptions const { publicKey, type } = convertPublicKey(fromPublicKey) if (!signingMethodTypes.includes(type)) { throw new Error(`unknown key type ${type}`) } - const submitterAccount = ( - 'address' in submitter ? submitter.address : submitter.id - ) as KiltAddress + const { submitterSigner, submitterAccount } = + extractSubmitterSignerAndAccount(submitter) const accountSigners = ( await Promise.all( @@ -77,7 +79,10 @@ export function createDid( ) if (signSubmittable) { - didCreation = await Blockchain.signTx(didCreation, submitter) + if (typeof submitterSigner === 'undefined') { + throw new Error('submitter does not include a secret key') + } + didCreation = await Blockchain.signTx(didCreation, submitterSigner) } return { diff --git a/packages/sdk-js/src/DidHelpers/transact.spec.ts b/packages/sdk-js/src/DidHelpers/transact.spec.ts index 1c4541e8f..3b919d78a 100644 --- a/packages/sdk-js/src/DidHelpers/transact.spec.ts +++ b/packages/sdk-js/src/DidHelpers/transact.spec.ts @@ -76,7 +76,7 @@ describe('transact', () => { expectedEvents: [ { section: 'attestation', method: 'AttestationCreated' }, ], - }).getSubmittable({ signSubmittable: false }) + }).getSubmittable() expect(txHex).toContain('0x') const parsed = mockedApi.tx(txHex) @@ -118,4 +118,26 @@ describe('transact', () => { value: confirmed.toJSON(), }) }) + + it('creates an unsigned submittable', async () => { + const { txHex } = await transact({ + didDocument, + api: mockedApi, + submitter: keypair.address, + signers: [keypair], + call: mockedApi.tx.attestation.add( + new Uint8Array(32).fill(1), + new Uint8Array(32).fill(1), + null + ), + expectedEvents: [ + { section: 'attestation', method: 'AttestationCreated' }, + ], + }).getSubmittable({ signSubmittable: false }) + + expect(txHex).toContain('0x') + const parsed = mockedApi.tx(txHex) + expect(parsed.method).toHaveProperty('section', 'did') + expect(parsed.method).toHaveProperty('method', 'submitDidCall') + }) }) diff --git a/packages/sdk-js/src/DidHelpers/transact.ts b/packages/sdk-js/src/DidHelpers/transact.ts index ad0474043..d8b1f33b8 100644 --- a/packages/sdk-js/src/DidHelpers/transact.ts +++ b/packages/sdk-js/src/DidHelpers/transact.ts @@ -6,18 +6,15 @@ */ import type { Extrinsic } from '@polkadot/types/interfaces' - import { Blockchain } from '@kiltprotocol/chain-helpers' import { authorizeTx, signersForDid } from '@kiltprotocol/did' import type { - KiltAddress, - SharedArguments, SubmittableExtrinsic, + SharedArguments, TransactionHandlers, } from '@kiltprotocol/types' - +import { extractSubmitterSignerAndAccount, submitImpl } from './common.js' import { checkResultImpl } from './checkResult.js' -import { submitImpl } from './common.js' /** * Instructs a transaction (state transition) as this DID (with this DID as the origin). @@ -35,7 +32,7 @@ export function transactInternal( const getSubmittable: TransactionHandlers['getSubmittable'] = async ( submitOptions: | { - signSubmittable?: boolean // default: true + signSubmittable?: boolean // default: false didNonce?: number | BigInt } | undefined = {} @@ -48,13 +45,12 @@ export function transactInternal( api, expectedEvents, } = options - const { didNonce, signSubmittable = true } = submitOptions + const { didNonce, signSubmittable = false } = submitOptions const call = await callFactory() const didSigners = await signersForDid(didDocument, ...signers) - const submitterAccount = ( - 'address' in submitter ? submitter.address : submitter.id - ) as KiltAddress + const { submitterSigner, submitterAccount } = + extractSubmitterSignerAndAccount(submitter) let authorized: SubmittableExtrinsic = await authorizeTx( didDocument, @@ -69,7 +65,10 @@ export function transactInternal( ) if (signSubmittable) { - authorized = await Blockchain.signTx(authorized, submitter) + if (typeof submitterSigner === 'undefined') { + throw new Error('submitter does not include a secret key') + } + authorized = await Blockchain.signTx(authorized, submitterSigner) } return { diff --git a/packages/types/src/Address.ts b/packages/types/src/Address.ts index 7bbd2b6b7..50a31f085 100644 --- a/packages/types/src/Address.ts +++ b/packages/types/src/Address.ts @@ -26,10 +26,10 @@ export type KiltAddress = KiltKeyringPair['address'] declare module '@polkadot/keyring' { function encodeAddress( key: HexString | Uint8Array | string, - ss58Format?: Prefix - ): string + ss58Format: 38 + ): KiltAddress function encodeAddress( key: HexString | Uint8Array | string, - ss58Format?: 38 - ): KiltAddress + ss58Format?: Prefix + ): string } diff --git a/packages/types/src/DidHelpers.ts b/packages/types/src/DidHelpers.ts index 2ea7f82de..a2f776ebb 100644 --- a/packages/types/src/DidHelpers.ts +++ b/packages/types/src/DidHelpers.ts @@ -5,21 +5,19 @@ * found in the LICENSE file in the root directory of this source tree. */ +import type { ApiPromise } from '@polkadot/api' +import type { SubmittableResultValue } from '@polkadot/api/types' +import type { GenericEvent } from '@polkadot/types' import type { Multikey } from '@kiltprotocol/utils' -import type { DidDocument } from './Did' -import type { - ApiPromise, - GenericEvent, - HexString, - KeyringPair, - SubmittableResultValue, -} from './Imported' import type { MultibaseKeyPair, MultibasePublicKey, SignerInterface, TransactionSigner, } from './Signers' +import { HexString, KeyringPair } from './Imported.js' +import { DidDocument } from './Did.js' +import { KiltAddress } from './Address.js' export interface TransactionResult { status: 'confirmed' | 'failed' | 'rejected' | 'unknown' @@ -105,7 +103,7 @@ export type SharedArguments = { didDocument: DidDocument api: ApiPromise signers: DidHelpersAcceptedSigners[] - submitter: KeyringPair | TransactionSigner + submitter: KeyringPair | TransactionSigner | MultibaseKeyPair | KiltAddress } type PublicKeyAndType = { diff --git a/tests/integration/didHelpers.spec.ts b/tests/integration/didHelpers.spec.ts index 1d7936b99..3e7c04b01 100644 --- a/tests/integration/didHelpers.spec.ts +++ b/tests/integration/didHelpers.spec.ts @@ -409,6 +409,25 @@ describe('transact', () => { Verifier.verifyCredential({ credential: issued }) ).resolves.toMatchObject({ verified: true }) }, 30_000) + + it('creates signed submittable', async () => { + const serialized = CType.toChain(cType) + const call = api.tx.ctype.add(serialized) + + const { txHex } = await DidHelpers.transact({ + api, + signers: [keypair], + submitter: paymentAccount, + didDocument, + call, + expectedEvents: [{ section: 'ctype', method: 'CTypeCreated' }], + }).getSubmittable({ signSubmittable: true }) + expect(txHex).toContain('0x') + const parsed = api.tx(txHex) + expect(parsed.method).toHaveProperty('section', 'did') + expect(parsed.method).toHaveProperty('method', 'submitDidCall') + expect(parsed.isSigned).toBe(true) + }, 30_000) }) afterAll(async () => { diff --git a/tests/integration/utils.ts b/tests/integration/utils.ts index c718a7e0d..be42dd2cc 100644 --- a/tests/integration/utils.ts +++ b/tests/integration/utils.ts @@ -111,7 +111,7 @@ export const devBob = Crypto.makeKeypairFromUri('//Bob') export const devCharlie = Crypto.makeKeypairFromUri('//Charlie') export function addressFromRandom(): KiltAddress { - return encodeAddress(randomAsU8a()) + return encodeAddress(randomAsU8a(), 38) } export async function isCtypeOnChain(cType: ICType): Promise { From a25c05a441a59675dff95868177e6e85b0a821fd Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 24 Jul 2024 13:32:34 +0200 Subject: [PATCH 69/70] fix: can't import utils in types --- packages/sdk-js/src/DidHelpers/common.ts | 4 +++- packages/sdk-js/src/DidHelpers/createDid.ts | 4 ++-- .../sdk-js/src/DidHelpers/verificationMethod.ts | 9 +++++---- packages/types/src/DidHelpers.ts | 14 +++++++------- packages/types/src/Signers.ts | 6 ++++++ packages/utils/src/Multikey.ts | 14 +++++--------- 6 files changed, 28 insertions(+), 23 deletions(-) diff --git a/packages/sdk-js/src/DidHelpers/common.ts b/packages/sdk-js/src/DidHelpers/common.ts index c32eb2557..692425494 100644 --- a/packages/sdk-js/src/DidHelpers/common.ts +++ b/packages/sdk-js/src/DidHelpers/common.ts @@ -47,7 +47,9 @@ export async function submitImpl( return submittable.checkResult(result) } -export function convertPublicKey(pk: DidHelpersAcceptedPublicKeyEncodings): { +export function convertPublicKey( + pk: DidHelpersAcceptedPublicKeyEncodings +): { publicKey: Uint8Array type: string } { diff --git a/packages/sdk-js/src/DidHelpers/createDid.ts b/packages/sdk-js/src/DidHelpers/createDid.ts index 86f484ca4..ab4f97b12 100644 --- a/packages/sdk-js/src/DidHelpers/createDid.ts +++ b/packages/sdk-js/src/DidHelpers/createDid.ts @@ -18,7 +18,7 @@ import type { SignerInterface, TransactionHandlers, } from '@kiltprotocol/types' -import { Crypto, Signers } from '@kiltprotocol/utils' +import { Crypto, type Multikey, Signers } from '@kiltprotocol/utils' import { checkResultImpl } from './checkResult.js' import { @@ -40,7 +40,7 @@ function implementsSignerInterface(input: any): input is SignerInterface { */ export function createDid( options: Omit & { - fromPublicKey: DidHelpersAcceptedPublicKeyEncodings + fromPublicKey: DidHelpersAcceptedPublicKeyEncodings } ): TransactionHandlers { const getSubmittable: TransactionHandlers['getSubmittable'] = async ( diff --git a/packages/sdk-js/src/DidHelpers/verificationMethod.ts b/packages/sdk-js/src/DidHelpers/verificationMethod.ts index 686c399b7..536ed3d09 100644 --- a/packages/sdk-js/src/DidHelpers/verificationMethod.ts +++ b/packages/sdk-js/src/DidHelpers/verificationMethod.ts @@ -12,13 +12,14 @@ import { urlFragmentToChain, } from '@kiltprotocol/did' import type { - DidUrl, - SubmittableExtrinsic, - VerificationRelationship, DidHelpersAcceptedPublicKeyEncodings, + DidUrl, SharedArguments, + SubmittableExtrinsic, TransactionHandlers, + VerificationRelationship, } from '@kiltprotocol/types' +import type { Multikey } from '@kiltprotocol/utils' import { convertPublicKey } from './common.js' import { transactInternal } from './transact.js' @@ -34,7 +35,7 @@ import { transactInternal } from './transact.js' */ export function setVerificationMethod( options: SharedArguments & { - publicKey: DidHelpersAcceptedPublicKeyEncodings + publicKey: DidHelpersAcceptedPublicKeyEncodings relationship: VerificationRelationship } ): TransactionHandlers { diff --git a/packages/types/src/DidHelpers.ts b/packages/types/src/DidHelpers.ts index a2f776ebb..27800f807 100644 --- a/packages/types/src/DidHelpers.ts +++ b/packages/types/src/DidHelpers.ts @@ -8,12 +8,12 @@ import type { ApiPromise } from '@polkadot/api' import type { SubmittableResultValue } from '@polkadot/api/types' import type { GenericEvent } from '@polkadot/types' -import type { Multikey } from '@kiltprotocol/utils' import type { MultibaseKeyPair, MultibasePublicKey, SignerInterface, TransactionSigner, + TypedKeypair, } from './Signers' import { HexString, KeyringPair } from './Imported.js' import { DidDocument } from './Did.js' @@ -106,12 +106,12 @@ export type SharedArguments = { submitter: KeyringPair | TransactionSigner | MultibaseKeyPair | KiltAddress } -type PublicKeyAndType = { - publicKey: Uint8Array - type: Multikey.KnownTypeString -} +type PublicKeyAndType = Pick< + TypedKeypair, + 'publicKey' | 'type' +> -export type DidHelpersAcceptedPublicKeyEncodings = +export type DidHelpersAcceptedPublicKeyEncodings = | MultibasePublicKey['publicKeyMultibase'] | MultibasePublicKey - | PublicKeyAndType // interface allows KeyringPair too + | PublicKeyAndType // interface allows KeyringPair too diff --git a/packages/types/src/Signers.ts b/packages/types/src/Signers.ts index 581824b1a..a6ceee32a 100644 --- a/packages/types/src/Signers.ts +++ b/packages/types/src/Signers.ts @@ -27,3 +27,9 @@ export type MultibaseSecretKey = { secretKeyMultibase: Base58BtcMultibaseString } export type MultibaseKeyPair = MultibasePublicKey & MultibaseSecretKey + +export type TypedKeypair = { + publicKey: Uint8Array + secretKey: Uint8Array + type: KeyTypes +} diff --git a/packages/utils/src/Multikey.ts b/packages/utils/src/Multikey.ts index ce3a486ef..4e14b21e8 100644 --- a/packages/utils/src/Multikey.ts +++ b/packages/utils/src/Multikey.ts @@ -15,6 +15,7 @@ import { MultibaseKeyPair, MultibasePublicKey, MultibaseSecretKey, + TypedKeypair, } from '@kiltprotocol/types' import { u8aConcat } from '@polkadot/util' import { base58Encode } from '@polkadot/util-crypto' @@ -57,15 +58,10 @@ function multibase58BtcKeyBytesEncoding( return `z${base58BtcEncodedKey}` } -type TypedKeypairWithOptionalSecretKey = { - publicKey: Uint8Array - secretKey?: Uint8Array - type: KeyTypes -} - -type TypedKeypair = Required< - TypedKeypairWithOptionalSecretKey -> +type TypedKeypairWithOptionalSecretKey = Partial< + Pick, 'secretKey'> +> & + Pick, 'publicKey' | 'type'> /** * Create a Multikey representation of a keypair, encoded in multibase-base58-btc, given its type and public/secret keys. From 465e54ba2e00dc14435a5e2df4e66845abc53683 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 24 Jul 2024 14:08:05 +0200 Subject: [PATCH 70/70] fix: bundle tests --- packages/did/src/Did.signature.ts | 4 +++- tests/bundle/bundle-test.ts | 12 ++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/did/src/Did.signature.ts b/packages/did/src/Did.signature.ts index 4339a32db..ea2d8f560 100644 --- a/packages/did/src/Did.signature.ts +++ b/packages/did/src/Did.signature.ts @@ -214,7 +214,9 @@ export async function signersForDid( keyType === keypair.type && u8aEq(publicKey, keypair.publicKey) } else if ('publicKey' in keypair) { keyMatches = ({ publicKey }) => u8aEq(publicKey, keypair.publicKey) - } else if (keypair.id.startsWith('z')) { + } else if (keypair.id?.startsWith(didDocument.id)) { + keyMatches = ({ id }) => keypair.id === id + } else if (keypair.id?.startsWith('z')) { keyMatches = ({ publicKeyMultibase }) => publicKeyMultibase === keypair.id } else if (isAddress(keypair.id)) { diff --git a/tests/bundle/bundle-test.ts b/tests/bundle/bundle-test.ts index 2c3370075..ca433f0c6 100644 --- a/tests/bundle/bundle-test.ts +++ b/tests/bundle/bundle-test.ts @@ -26,7 +26,7 @@ const { Holder, DidResolver, signAndSubmitTx, - signerFromKeypair, + getSignersForKeypair, } = kilt async function authorizeTx( @@ -63,9 +63,9 @@ async function createFullDid( ) { const api = ConfigService.get('api') - const signer: SignerInterface = await signerFromKeypair({ + const [signer] = await getSignersForKeypair({ keypair, - algorithm: 'Ed25519', + type: 'Ed25519', }) const address = signer.id const getSigners: ( @@ -129,10 +129,10 @@ async function runAll() { 3, ]), } - const payerSigner = await signerFromKeypair<'Ed25519', KiltAddress>({ + const [payerSigner] = (await getSignersForKeypair({ keypair: faucet, - algorithm: 'Ed25519', - }) + type: 'Ed25519', + })) as Array> console.log('faucet signer created')