From 994437d2c8635f4c6ab2c94f34e68f2e676f466e Mon Sep 17 00:00:00 2001 From: Antonio Date: Thu, 2 May 2024 11:14:39 +0300 Subject: [PATCH] feat: better API surface and atomic features (#8) * Intermediate refactoring work * Make defaultValues uniform * Fix up stuff * Add flag to disable node verifier process * All tests passing * Update comments * Yarn lint * Lint * Fix * Yarn version --- .yarn/versions/e245aa3a.yml | 2 + package.json | 4 +- src/dipProof/extensions/index.ts | 10 + .../extensions/timeBoundDidSignature.ts | 154 +++++++++ src/dipProof/extensions/types.ts | 8 + src/dipProof/index.ts | 11 + src/dipProof/subjectIdentity.ts | 78 +++++ src/dipProof/types.ts | 9 + src/index.ts | 6 +- src/runtime.ts | 29 -- src/sibling.ts | 201 ++++++------ src/stateProof/index.ts | 11 + src/stateProof/providerStateRoot.ts | 75 +++++ src/stateProof/subjectDipCommitment.ts | 60 ++++ src/stateProof/types.ts | 9 + src/types.ts | 10 + src/utils.ts | 308 ------------------ .../develop.test.ts | 93 ++++-- .../develop-zombienet.toml | 1 + .../develop.test.ts | 98 ++++-- tests/utils.ts | 97 +----- 21 files changed, 672 insertions(+), 602 deletions(-) create mode 100644 .yarn/versions/e245aa3a.yml create mode 100644 src/dipProof/extensions/index.ts create mode 100644 src/dipProof/extensions/timeBoundDidSignature.ts create mode 100644 src/dipProof/extensions/types.ts create mode 100644 src/dipProof/index.ts create mode 100644 src/dipProof/subjectIdentity.ts create mode 100644 src/dipProof/types.ts delete mode 100644 src/runtime.ts create mode 100644 src/stateProof/index.ts create mode 100644 src/stateProof/providerStateRoot.ts create mode 100644 src/stateProof/subjectDipCommitment.ts create mode 100644 src/stateProof/types.ts create mode 100644 src/types.ts delete mode 100644 src/utils.ts diff --git a/.yarn/versions/e245aa3a.yml b/.yarn/versions/e245aa3a.yml new file mode 100644 index 0000000..80749ae --- /dev/null +++ b/.yarn/versions/e245aa3a.yml @@ -0,0 +1,2 @@ +declined: + - "@kiltprotocol/dip-sdk" diff --git a/package.json b/package.json index d2f22af..25a272e 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,8 @@ "check": "tsc -p tsconfig.json", "clean": "yarn rimraf -g */{cjs,esm}", "clean:docs": "rimraf docs/api", - "lint": "eslint --ext .ts . && prettier -c .", - "lint:fix": "prettier -w . && eslint --fix --ext .ts .", + "lint": "eslint --ext .ts .", + "lint:fix": "yarn lint --fix", "prepublish": "yarn exec cp -f ../../LICENSE .", "publish": "yarn npm publish --access=public", "test:e2e:peregrine-provider": "yarn build && vitest run tests/peregrine-dip-consumer-template", diff --git a/src/dipProof/extensions/index.ts b/src/dipProof/extensions/index.ts new file mode 100644 index 0000000..81a48e5 --- /dev/null +++ b/src/dipProof/extensions/index.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) 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. + */ + +export type * from "./types.js" + +export * as timeBoundDidSignature from "./timeBoundDidSignature.js" diff --git a/src/dipProof/extensions/timeBoundDidSignature.ts b/src/dipProof/extensions/timeBoundDidSignature.ts new file mode 100644 index 0000000..5acd3d6 --- /dev/null +++ b/src/dipProof/extensions/timeBoundDidSignature.ts @@ -0,0 +1,154 @@ +/** + * Copyright (c) 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 { toChain as didToChain } from "@kiltprotocol/did" +import { BN, u8aToHex } from "@polkadot/util" + +import type { + DidUri, + SignExtrinsicCallback, + VerificationKeyRelationship, + VerificationKeyType, +} from "@kiltprotocol/types" +import type { ApiPromise } from "@polkadot/api" +import type { KeyringPair } from "@polkadot/keyring/types" +import type { Call, Hash } from "@polkadot/types/interfaces" +import type { Option } from "@polkadot/types-codec" +import type { Codec } from "@polkadot/types-codec/types" + +const defaultValues = { + accountIdRuntimeType: async () => "AccountId", + blockNumberRuntimeType: async () => "u64", + identityDetailsRuntimeType: async () => "Option", + validUntilOffset: async () => new BN(50), +} + +/** + * The options regarding the provider chain provided when generating a cross-chain mortal DID signature. + */ +export type TimeBoundDidSignatureProviderOpts = { + /** The `DidUri` of the DIP subject that is performing the cross-chain operation. */ + didUri: DidUri + /** The `VerificationKeyRelationship` to use from the provided DID Document to sign the cross-chain payload. */ + keyRelationship: VerificationKeyRelationship + /** The list of `Signers` to use to sign the cross-chain payload. */ + signer: SignExtrinsicCallback +} +/** + * The options regarding the consumer chain when generating a cross-chain mortal DID signature. + */ +export type TimeBoundDidSignatureConsumerOpts = { + /** The runtime definition of an `AccountId`. If not provided, the `AccountId` type is used. */ + accountIdRuntimeType?: string + /** The `ApiPromise` instance of the provider chain. */ + api: ApiPromise + /** The runtime definition of a `BlockNumber`. If not provided, the `u64` type is used. */ + blockNumberRuntimeType?: string + /** The `Call` to DID-authorize. */ + call: Call + /** The genesis hash to use for the DID signature. If not provided, it is retrieved at runtime from the consumer chain. */ + genesisHash?: Hash + /** The runtime definition of the `IdentityDetails`. If not provided, the `Option` type is used. */ + identityDetailsRuntimeType?: string + /** The address of the submitter account on the consumer chain. */ + submitterAddress: KeyringPair["address"] + /** The block number until which the DID signature is to be considered fresh. If not provided, the latest best block number + an offset of 50 blocks is used. */ + validUntil?: BN +} +/** + * The options object provided when generating a cross-chain mortal DID signature. + */ +export type TimeBoundDidSignatureOpts = { + consumer: TimeBoundDidSignatureConsumerOpts + provider: TimeBoundDidSignatureProviderOpts +} +/** + * The cross-chain DID signature details to be included in the cross-chain DIP proof. + */ +export type TimeBoundDidSignatureRes = { + signature: Uint8Array + type: VerificationKeyType + validUntil: BN +} +/** + * Generate a DID signature to be used in conjunction with a DIP proof to DID-authorize a cross-chain operation. + * + * @param params The signature generation parameters. + * + * @returns The generated cross-chain DID signature. + */ +export async function generateDidSignature({ + provider: { didUri, signer, keyRelationship }, + consumer: { + api, + call, + submitterAddress, + // Optional + accountIdRuntimeType, + blockNumberRuntimeType, + genesisHash, + identityDetailsRuntimeType, + validUntil, + }, +}: TimeBoundDidSignatureOpts): Promise { + const blockNumber: BN = + validUntil ?? + (await api.query.system.number()) + .toBn() + .add(await defaultValues.validUntilOffset()) + const genesis = genesisHash ?? (await api.query.system.blockHash(0)) + const actualIdentityDetailsRuntimeType = + identityDetailsRuntimeType ?? + (await defaultValues.identityDetailsRuntimeType()) + const identityDetails = ( + await api.query.dipConsumer.identityEntries>( + didToChain(didUri), + ) + ).unwrapOr(api.createType(actualIdentityDetailsRuntimeType, null)) + + const signaturePayload = api + .createType( + `(Call, ${identityDetailsRuntimeType}, ${ + accountIdRuntimeType ?? (await defaultValues.accountIdRuntimeType()) + }, ${ + blockNumberRuntimeType ?? (await defaultValues.blockNumberRuntimeType()) + }, Hash)`, + [call, identityDetails, submitterAddress, blockNumber, genesis], + ) + .toU8a() + const { signature, keyType } = await signer({ + data: signaturePayload, + did: didUri, + keyRelationship, + }) + return { + validUntil: blockNumber, + signature, + type: keyType, + } +} + +/** + * Transform a [[TimeBoundDidSignatureRes]] into an object that can be used to extend the basic DIP proof generated by [[generateDipSiblingBaseProof]]. + * + * @param params The cross-chain DID signature as generated by [[generateDidSignature]]. + * + * @returns The CODEC-ready version of the signature. + */ +export function toChain( + signature: TimeBoundDidSignatureRes, +): Record { + const encodedSignature = { + signature: { + [signature.type]: u8aToHex(signature.signature), + }, + validUntil: signature.validUntil, + } as any as Codec + return { + signature: encodedSignature, + } +} diff --git a/src/dipProof/extensions/types.ts b/src/dipProof/extensions/types.ts new file mode 100644 index 0000000..ef415a5 --- /dev/null +++ b/src/dipProof/extensions/types.ts @@ -0,0 +1,8 @@ +/** + * Copyright (c) 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. + */ + +export type * from "./timeBoundDidSignature.js" diff --git a/src/dipProof/index.ts b/src/dipProof/index.ts new file mode 100644 index 0000000..f4a5bd0 --- /dev/null +++ b/src/dipProof/index.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) 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. + */ + +export type * from "./types.js" + +export * from "./subjectIdentity.js" +export * as extensions from "./extensions/index.js" diff --git a/src/dipProof/subjectIdentity.ts b/src/dipProof/subjectIdentity.ts new file mode 100644 index 0000000..5ebbfc8 --- /dev/null +++ b/src/dipProof/subjectIdentity.ts @@ -0,0 +1,78 @@ +/** + * Copyright (c) 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 { toChain } from "@kiltprotocol/did" + +import type { PalletDidLookupLinkableAccountLinkableAccountId } from "@kiltprotocol/augment-api" +import type { DidUri, DidKey } from "@kiltprotocol/types" +import type { ApiPromise } from "@polkadot/api" +import type { Hash } from "@polkadot/types/interfaces" +import type { Codec } from "@polkadot/types-codec/types" + +/** + * The options object provided when generating a DIP identity proof. + */ +export type DipIdentityProofOpts = { + /** The `DID` of the subject. */ + didUri: DidUri + /** The list of DID verification methods to include in the DIP proof and to reveal to the consumer chain. */ + keyIds: Array + /** A flag indicating whether the web3name should be included in the DIP proof. */ + includeWeb3Name: boolean + /** The list of accounts linked to the DID to include in the DIP proof and to reveal to the consumer chain. */ + linkedAccounts: readonly PalletDidLookupLinkableAccountLinkableAccountId[] + /** The `ApiPromise` instance for the provider chain. */ + providerApi: ApiPromise + /** The version of the DIP proof to generate. */ + version: number +} +/** + * The response object for a generated DIP proof. + */ +export type DipIdentityProofRes = { + /** The generated storage proof. */ + proof: { + /** The Merkle proof blinded (not revealed) leaves. */ + blinded: Codec + /** The Merkle proof revealed leaves. */ + revealed: Codec + } + /** The Merkle root hash which the proof is anchored to. */ + root: Hash +} +/** + * Generate a DIP proof that reveals the specified information about the DID subject. + * + * @param params The DIP proof params. + * + * @returns The generated basic DIP proof that reveals the specified parts of the DID Document, optionally revealing its web3name and any linked accounts as specified. + */ +export async function generateDipIdentityProof({ + didUri, + keyIds, + includeWeb3Name, + linkedAccounts, + providerApi, + version, +}: DipIdentityProofOpts): Promise { + const proof = await providerApi.call.dipProvider.generateProof({ + identifier: toChain(didUri), + version, + proofKeys: keyIds.map((keyId) => keyId.substring(1)), + accounts: linkedAccounts, + shouldIncludeWeb3Name: includeWeb3Name, + }) + + if (proof.isErr) { + throw new Error(providerApi.findError(proof.asErr.toHex()).docs.join("\n")) + } + + // TODO: Better way to cast this? + const okProof = proof.asOk.toJSON() as any + + return okProof +} diff --git a/src/dipProof/types.ts b/src/dipProof/types.ts new file mode 100644 index 0000000..5bdd51d --- /dev/null +++ b/src/dipProof/types.ts @@ -0,0 +1,9 @@ +/** + * Copyright (c) 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. + */ + +export type * from "./subjectIdentity.js" +export type * from "./extensions/types.js" diff --git a/src/index.ts b/src/index.ts index f08d620..3d5492b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,8 @@ * @module @kiltprotocol/dip-sdk */ -export * from "./runtime.js" +export type * from "./types.js" + +export * as stateProof from "./stateProof/index.js" +export * as dipProof from "./dipProof/index.js" export * from "./sibling.js" -export * from "./utils.js" diff --git a/src/runtime.ts b/src/runtime.ts deleted file mode 100644 index 53c25fa..0000000 --- a/src/runtime.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 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 { DefinitionsCall } from "@polkadot/types/types" - -export const dipProviderCalls: DefinitionsCall = { - DipProvider: [ - { - methods: { - generate_proof: { - description: - "Generate a Merkle proof for the DIP protocol for the specified request parameters.", - params: [ - { - name: "request", - type: "DipProofRequest", - }, - ], - type: "Result", - }, - }, - version: 1, - }, - ], -} diff --git a/src/sibling.ts b/src/sibling.ts index 8b67d31..8cc799f 100644 --- a/src/sibling.ts +++ b/src/sibling.ts @@ -7,34 +7,33 @@ import { toChain } from "@kiltprotocol/did" import { ApiPromise } from "@polkadot/api" -import { u8aToHex } from "@polkadot/util" +import { BN } from "@polkadot/util" -import { - defaultValues, - generateDipCommitmentProof, - generateDipDidSignature, - generateDipIdentityProof, - generateProviderStateRootProof, -} from "./utils.js" +import { generateDipIdentityProof } from "./dipProof/subjectIdentity.js" +import { generateProviderStateRootProof } from "./stateProof/providerStateRoot.js" +import { generateDipCommitmentProof } from "./stateProof/subjectDipCommitment.js" +import type { DipIdentityProofRes } from "./dipProof/subjectIdentity.js" +import type { ProviderStateRootProofRes } from "./stateProof/providerStateRoot.js" +import type { DipCommitmentProofRes } from "./stateProof/subjectDipCommitment.js" import type { PalletDidLookupLinkableAccountLinkableAccountId } from "@kiltprotocol/augment-api" -import type { - DidUri, - VerificationKeyRelationship, - SubmittableExtrinsic, - DidKey, - SignExtrinsicCallback, - BN, -} from "@kiltprotocol/types" -import type { KeyringPair } from "@polkadot/keyring/types" -import type { Call, Hash } from "@polkadot/types/interfaces" +import type { DidUri, DidKey, SubmittableExtrinsic } from "@kiltprotocol/types" +import type { Call } from "@polkadot/types/interfaces" + +const defaultValues = { + includeWeb3Name: async () => false, + linkedAccounts: async () => [], + providerBlockHeight: async (providerApi: ApiPromise) => { + const providerLastFinalizedBlockHash = + await providerApi.rpc.chain.getFinalizedHead() + return providerApi.rpc.chain + .getHeader(providerLastFinalizedBlockHash) + .then((h) => h.number.toBn()) + }, +} /** The DIP proof params. */ -export type DipSiblingProofInput = { - /** The `Call` on the consumer chain that requires a DIP origin. */ - call: Call - /** The `ApiPromise` instance for the consumer chain. */ - consumerApi: ApiPromise +export type DipSiblingBaseProofInput = { /** The DID URI of the DIP subject that is performing the cross-chain operation. */ didUri: DidUri /** The verification method IDs of the DID to be revealed in the cross-chain operation. */ @@ -45,133 +44,125 @@ export type DipSiblingProofInput = { providerApi: ApiPromise /** The `ApiPromise` instance for the parent relay chain. */ relayApi: ApiPromise - /** The signing callback to sign the cross-chain transaction. */ - signer: SignExtrinsicCallback - /** The address of the tx submitter on the consumer chain. */ - submitterAddress: KeyringPair["address"] - /** The `VerificationKeyRelationship` required for the DIP operation to be authorized on the relay chain. */ - keyRelationship: VerificationKeyRelationship - /** The block number until which the DID signature is to be considered fresh. If not provided, the latest best block number + an offset of 50 is used. */ - validUntil?: BN - /** The genesis hash of the consumer chain to use for the DID signature. If not provided, it is retrieved at runtime from the consumer chain. */ - genesisHash?: Hash /** The block number of the provider to use for the generation of the DIP proof. If not provided, the latest finalized block number is used. */ providerBlockHeight?: BN - /** The runtime type definition for an `AccountId` on the consumer chain. If not provided, the `AccountId` type is used. */ - accountIdRuntimeType?: string - /** The runtime type definition for a `BlockNumber` on the consumer chain. If not provided, the `u64` type is used. */ - blockNumberRuntimeType?: string - /** The runtime type definition for the `IdentityDetails` on the consumer chain. If not provided, the `Option` type, representing a simple nonce, is used. */ - identityDetailsRuntimeType?: string /** Flag indicating whether the generated DIP proof should include the web3name of the DID subject. If not provided, the web3name is not revealed. */ includeWeb3Name?: boolean /** The list of linked accounts to reveal in the generated DIP proof. If not provided, no account is revealed. */ linkedAccounts?: readonly PalletDidLookupLinkableAccountLinkableAccountId[] } +/** The DIP proof result. */ +export type DipSiblingBaseProofRes = { + providerHeadProof: ProviderStateRootProofRes + dipCommitmentProof: DipCommitmentProofRes + dipProof: DipIdentityProofRes + proofVersion: number +} + /** - * Generate a submittable extrinsic for the provided call which includes a complete DIP proof according to the parameters provided, to be used on a consumer chain of which the provider chain is a sibling. + * Generate a base DIP proof according to the parameters provided, to be used on a consumer chain of which the provider chain is a sibling. + * + * The generated proof only contains parts of the DID Document of the subject. + * Any additional components that the consumer chain requires, e.g., a cross-chain DID signature, or the presentation of some claims about the subject, are not part of the generated proof. + * This SDK contains an `extensions` section in which chain-specific proof formats could be added, if needed. * - * @param params The DIP proof params. + * @param params The DIP proof generation parameters. * - * @returns The `SubmittableExtrinsic` containing the signed cross-chain operation, that must be submitted by the account specified as the `submitterAddress` parameter. + * @returns The [[DipSiblingBaseProofRes]] containing the basic DIP proof components revealing parts of a DID Document anchored to a specific state root and block number on the provider chain, without any consumer-specific logic. */ -export async function generateDipAuthorizedTxForSibling({ - call, - consumerApi, +export async function generateDipSiblingBaseProof({ didUri, keyIds, proofVersion, providerApi, relayApi, - signer, - submitterAddress, - keyRelationship, - // Optional - validUntil, - genesisHash, providerBlockHeight, - // With defaults - accountIdRuntimeType = defaultValues.accountIdRuntimeType, - blockNumberRuntimeType = defaultValues.blockNumberRuntimeType, - identityDetailsRuntimeType = defaultValues.identityDetailsRuntimeType, - includeWeb3Name = defaultValues.includeWeb3Name, - linkedAccounts = defaultValues.linkedAccounts, -}: DipSiblingProofInput): Promise { - const { - proof: { proof: providerStateRootProof }, - providerBlockHeight: providerStateRootProofProviderBlockHeight, - relayBlockHeight: providerStateRootProofRelayBlockHeight, - } = await generateProviderStateRootProof({ + includeWeb3Name, + linkedAccounts, +}: DipSiblingBaseProofInput): Promise { + const actualProviderBlockHeight = + providerBlockHeight ?? + (await defaultValues.providerBlockHeight(providerApi)) + const providerHeadProof = await generateProviderStateRootProof({ relayApi, providerApi, - providerBlockHeight, + providerBlockHeight: actualProviderBlockHeight, + proofVersion, }) // Proof of commitment must be generated with the state root at the block before the last one finalized. const dipRootProofBlockHash = await providerApi.rpc.chain.getBlockHash( - providerStateRootProofProviderBlockHeight.subn(1), + actualProviderBlockHeight.subn(1), ) - - const { - proof: { proof: dipCommitmentProof }, - } = await generateDipCommitmentProof({ + const dipCommitmentProof = await generateDipCommitmentProof({ didUri, providerApi, providerBlockHash: dipRootProofBlockHash, version: proofVersion, }) - const { proof: dipIdentityProof } = await generateDipIdentityProof({ + const dipProof = await generateDipIdentityProof({ didUri, providerApi, keyIds, - linkedAccounts, + linkedAccounts: linkedAccounts || (await defaultValues.linkedAccounts()), version: proofVersion, - includeWeb3Name, + includeWeb3Name: includeWeb3Name || (await defaultValues.includeWeb3Name()), }) - const { - validUntil: didSignatureExpirationBlockNumber, - signature: didSignature, - type: didSignatureType, - } = await generateDipDidSignature({ - provider: { - didUri, - signer, - keyRelationship, - }, - consumer: { - api: consumerApi, - call, - submitterAddress, - accountIdRuntimeType, - validUntil, - blockNumberRuntimeType, - genesisHash, - identityDetailsRuntimeType, - }, - }) + return { + providerHeadProof, + dipCommitmentProof, + dipProof, + proofVersion, + } +} - return consumerApi.tx.dipConsumer.dispatchAs( +/** The params to create an extrinsic containing a cross-chain DIP proof and operation. */ +export type GenerateDipSubmittableExtrinsicInput = { + /** Any consumer-specific pieces of information to be included in the DIP proof beyond proof-of-DID. */ + additionalProofElements: Record + /** The [[ApiPromise]] instance of the consumer chain. */ + api: ApiPromise + /** [[DipSiblingBaseProofRes]] as generated by [[generateDipSiblingBaseProof]]. */ + baseDipProof: DipSiblingBaseProofRes + /** The [[Call]] on the consumer chain that requires a DIP origin to be authorized. */ + call: Call + /** The [[DidUri]] of the subject performing the cross-chain operation. */ + didUri: DidUri +} + +/** + * Extend a [[DipSiblingBaseProofRes]] proof with consumer-specific components, and compiles the `dispatchAs` extrinsic following the consumer's type registry. + * + * @param params The consumer information. + * + * @returns A [[SubmittableExtrinsic]] that refers to a [[Call]] on the consumer chain being dispatched by the specified [[DidUri]]. + */ +export function generateDipSubmittableExtrinsic({ + additionalProofElements, + api, + baseDipProof, + call, + didUri, +}: GenerateDipSubmittableExtrinsicInput): SubmittableExtrinsic { + const { proofVersion, ...dipProof } = baseDipProof + + return api.tx.dipConsumer.dispatchAs( toChain(didUri), { [`V${proofVersion}`]: { providerHeadProof: { - relayBlockNumber: providerStateRootProofRelayBlockHeight, - proof: providerStateRootProof, + relayBlockNumber: dipProof.providerHeadProof.relayBlockHeight, + proof: dipProof.providerHeadProof.proof.proof, }, - dipCommitmentProof, + dipCommitmentProof: dipProof.dipCommitmentProof.proof.proof, dipProof: { - blinded: dipIdentityProof.blinded, - revealed: dipIdentityProof.revealed, - }, - signature: { - signature: { - [didSignatureType]: u8aToHex(didSignature), - }, - validUntil: didSignatureExpirationBlockNumber, + blinded: dipProof.dipProof.proof.blinded, + revealed: dipProof.dipProof.proof.revealed, }, + ...additionalProofElements, }, }, call, diff --git a/src/stateProof/index.ts b/src/stateProof/index.ts new file mode 100644 index 0000000..ebccffe --- /dev/null +++ b/src/stateProof/index.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) 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. + */ + +export type * from "./types.js" + +export * from "./providerStateRoot.js" +export * from "./subjectDipCommitment.js" diff --git a/src/stateProof/providerStateRoot.ts b/src/stateProof/providerStateRoot.ts new file mode 100644 index 0000000..6f23550 --- /dev/null +++ b/src/stateProof/providerStateRoot.ts @@ -0,0 +1,75 @@ +/** + * Copyright (c) 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 { BN } from "@polkadot/util" + +import type { ApiPromise } from "@polkadot/api" +import type { ReadProof } from "@polkadot/types/interfaces" + +/** + * The options object provided when generating a proof for the provider state. + * + * Proof generation logic depends on the value of `proofVersion`. + * For more details about what each `proofVersion` provides, please refer to our docs. + */ +export type ProviderStateRootProofOpts = { + /** The `ApiPromise` instance for the provider chain. */ + providerApi: ApiPromise + /** The `ApiPromise` instance for the relay chain. */ + relayApi: ApiPromise + /** The block number on the provider chain to use for the proof. If not provided, the latest finalized block number for the provider is used. */ + providerBlockHeight: BN + /** The version of the parachain state proof to generate. */ + proofVersion: number +} +/** + * The response object containing the provider state proof. + */ +export type ProviderStateRootProofRes = { + /** The raw state proof for the provider state. */ + proof: ReadProof + /** The block number of the relaychain which the proof is anchored to. */ + relayBlockHeight: BN +} +/** + * Generate a proof for the state root of the provider. + * + * The value and type of the proof depends on the version specified. + * For more details about what each `proofVersion` provides, please refer to our docs. + * + * @param params The state proof params. + * + * @returns The generated state proof. + */ +export async function generateProviderStateRootProof({ + providerApi, + relayApi, + providerBlockHeight, // `proofVersion` is not used, for now, but it's added to avoid introducing unnecessary breaking changes + // proofVersion, +}: ProviderStateRootProofOpts): Promise { + const providerBlockHash = + await providerApi.rpc.chain.getBlockHash(providerBlockHeight) + const providerApiAtBlock = await providerApi.at(providerBlockHash) + const providerParaId = + await providerApiAtBlock.query.parachainInfo.parachainId() + const relayParentBlockNumber = + await providerApiAtBlock.query.parachainSystem.lastRelayChainBlockNumber() + // This refers to the previously finalized block, we need the current one. + const relayParentBlockHash = await relayApi.rpc.chain.getBlockHash( + relayParentBlockNumber, + ) + + const proof = await relayApi.rpc.state.getReadProof( + [relayApi.query.paras.heads.key(providerParaId)], + relayParentBlockHash, + ) + + return { + proof, + relayBlockHeight: relayParentBlockNumber.toBn(), + } +} diff --git a/src/stateProof/subjectDipCommitment.ts b/src/stateProof/subjectDipCommitment.ts new file mode 100644 index 0000000..3aa4df4 --- /dev/null +++ b/src/stateProof/subjectDipCommitment.ts @@ -0,0 +1,60 @@ +/** + * Copyright (c) 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 { toChain } from "@kiltprotocol/did" + +import type { DidUri } from "@kiltprotocol/types" +import type { ApiPromise } from "@polkadot/api" +import type { Hash, ReadProof } from "@polkadot/types/interfaces" + +/** + * The options object provided when generating a DIP commitment proof. + */ +export type DipCommitmentProofOpts = { + /** The `DidUri` of the subject. */ + didUri: DidUri + /** The `ApiPromise` instance for the provider chain. */ + providerApi: ApiPromise + /** The block hash on the provider chain to use for the state proof. */ + providerBlockHash: Hash + /** The version of the identity commitment to generate the state proof for. */ + version: number +} +/** + * The response object for a DIP commitment proof. + */ +export type DipCommitmentProofRes = { + /** The storage proof for the DIP commitment value. */ + proof: ReadProof +} +/** + * Generate a state proof for the value of a DIP identity commitment of a specific version on the specified provider chain. + * + * For more details about what each `version` provides, please refer to our docs. + * + * @param params The state proof params. + * + * @returns The generated state proof. + */ +export async function generateDipCommitmentProof({ + didUri, + providerApi, + providerBlockHash, + version, +}: DipCommitmentProofOpts): Promise { + const proof = await providerApi.rpc.state.getReadProof( + [ + providerApi.query.dipProvider.identityCommitments.key( + toChain(didUri), + version, + ), + ], + providerBlockHash, + ) + + return { proof } +} diff --git a/src/stateProof/types.ts b/src/stateProof/types.ts new file mode 100644 index 0000000..3f70f52 --- /dev/null +++ b/src/stateProof/types.ts @@ -0,0 +1,9 @@ +/** + * Copyright (c) 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. + */ + +export type * from "./providerStateRoot.js" +export type * from "./subjectDipCommitment.js" diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..2f3b4c1 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) 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. + */ + +export type * from "./stateProof/index.js" +export type * from "./dipProof/index.js" +export type * from "./sibling.js" diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index 0645033..0000000 --- a/src/utils.ts +++ /dev/null @@ -1,308 +0,0 @@ -/** - * Copyright (c) 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 { toChain } from "@kiltprotocol/did" -import { BN } from "@polkadot/util" - -import type { PalletDidLookupLinkableAccountLinkableAccountId } from "@kiltprotocol/augment-api" -import type { - DidUri, - SignExtrinsicCallback, - DidKey, - VerificationKeyRelationship, - VerificationKeyType, -} from "@kiltprotocol/types" -import type { ApiPromise } from "@polkadot/api" -import type { KeyringPair } from "@polkadot/keyring/types" -import type { Call, Hash, ReadProof } from "@polkadot/types/interfaces" -import type { Option } from "@polkadot/types-codec" -import type { Codec } from "@polkadot/types-codec/types" - -export const defaultValues = { - accountIdRuntimeType: "AccountId", - blockNumberRuntimeType: "u64", - identityDetailsRuntimeType: "Option", - includeWeb3Name: false, - linkedAccounts: [], - validUntilOffset: new BN(50), -} - -/** - * The options object provided when generating a Provider state proof. - */ -export type ProviderStateRootProofOpts = { - /** The `ApiPromise` instance for the provider chain. */ - providerApi: ApiPromise - /** The `ApiPromise` instance for the relay chain. */ - relayApi: ApiPromise - /** The block number on the provider chain to use for the state proof. If not provided, the latest finalized block number is used. */ - providerBlockHeight?: BN -} -/** - * The response object containing the provider state root proof. - */ -export type ProviderStateRootProofRes = { - /** The state proof for the provider header. */ - proof: ReadProof - /** The block number of the provider which the proof is anchored to. */ - providerBlockHeight: BN - /** The block number of the relaychain which the proof is anchored to. */ - relayBlockHeight: BN -} -/** - * Generate a state proof that proofs the head of the specified parachain. - * - * @param params The state proof params. - * - * @returns The generated state proof. - */ -export async function generateProviderStateRootProof({ - providerApi, - relayApi, - // Optional - providerBlockHeight, -}: ProviderStateRootProofOpts): Promise { - const [providerBlockNumber, providerBlockHash] = await (async () => { - if (providerBlockHeight !== undefined) { - const blockHash = - await providerApi.rpc.chain.getBlockHash(providerBlockHeight) - return [providerBlockHeight, blockHash] - } - const providerLastFinalizedBlockHash = - await providerApi.rpc.chain.getFinalizedHead() - const providerLastFinalizedBlockHeight = await providerApi.rpc.chain - .getHeader(providerLastFinalizedBlockHash) - .then((h) => h.number.toBn()) - return [providerLastFinalizedBlockHeight, providerLastFinalizedBlockHash] - })() - const providerApiAtBlock = await providerApi.at(providerBlockHash) - const providerChainId = - await providerApiAtBlock.query.parachainInfo.parachainId() - const relayParentBlockNumber = - await providerApiAtBlock.query.parachainSystem.lastRelayChainBlockNumber() - // This refers to the previously finalized block, we need the current one. - const relayParentBlockHash = await relayApi.rpc.chain.getBlockHash( - relayParentBlockNumber, - ) - - const proof = await relayApi.rpc.state.getReadProof( - [relayApi.query.paras.heads.key(providerChainId)], - relayParentBlockHash, - ) - - return { - proof, - providerBlockHeight: providerBlockNumber, - relayBlockHeight: (relayParentBlockNumber as any).toNumber(), - } -} - -/** - * The options object provided when generating a DIP commitment proof. - */ -export type DipCommitmentProofOpts = { - /** The `DidUri` of the subject. */ - didUri: DidUri - /** The `ApiPromise` instance for the provider chain. */ - providerApi: ApiPromise - /** The block hash on the provider chain to use for the state proof. */ - providerBlockHash: Hash - /** The version of the identity commitment to generate the state proof for. */ - version: number -} -/** - * The response object for a DIP commitment proof. - */ -export type DipCommitmentProofRes = { - /** The storage proof for the DIP commitment value. */ - proof: ReadProof -} -/** - * Generate a state proof that proofs the value of an identity commitment on the specified provider chain. - * - * @param params The state proof params. - * - * @returns The generated state proof. - */ -export async function generateDipCommitmentProof({ - didUri: did, - providerApi, - providerBlockHash, - version, -}: DipCommitmentProofOpts): Promise { - const proof = await providerApi.rpc.state.getReadProof( - [ - providerApi.query.dipProvider.identityCommitments.key( - toChain(did), - version, - ), - ], - providerBlockHash, - ) - - return { proof } -} - -/** - * The options object provided when generating a DIP identity proof. - */ -export type DipIdentityProofOpts = { - /** The `Did` of the subject. */ - didUri: DidUri - /** The list of DID verification methods to include in the DIP proof and to reveal to the consumer chain. */ - keyIds: Array - /** A flag indicating whether the web3name should be included in the DIP proof. */ - includeWeb3Name: boolean - /** The list of accounts linked to the DID ot include in the DIP proof and to reveal to the consumer chain. */ - linkedAccounts: readonly PalletDidLookupLinkableAccountLinkableAccountId[] - /** The `ApiPromise` instance for the provider chain. */ - providerApi: ApiPromise - /** The version of the DIP proof to generate. */ - version: number -} -/** - * The response object for a generated DIP proof. - */ -export type DipIdentityProofRes = { - /** The generated storage proof. */ - proof: { - /** The Merkle proof blinded (not revealed) leaves. */ - blinded: Codec - /** The Merkle proof revealed leaves. */ - revealed: Codec - } - /** The Merkle root hash which the proof is anchored to. */ - root: Hash -} -/** - * Generate a DIP proof that reveals the specified information about the DID subject. - * - * @param params The DIP proof params. - * - * @returns The generated DIP proof. - */ -export async function generateDipIdentityProof({ - didUri: did, - keyIds, - includeWeb3Name, - linkedAccounts, - providerApi, - version, -}: DipIdentityProofOpts): Promise { - const proof = await providerApi.call.dipProvider.generateProof({ - identifier: toChain(did), - version, - proofKeys: keyIds.map((keyId) => keyId.substring(1)), - accounts: linkedAccounts, - shouldIncludeWeb3Name: includeWeb3Name, - }) - - if (proof.isErr) { - throw new Error(providerApi.findError(proof.asErr.toHex()).docs.join("\n")) - } - - // TODO: Better way to cast this? - const okProof = proof.asOk.toJSON() as any - - return okProof -} - -/** - * The Provider options object provided when generating a DIP DID signature. - */ -export type DipDidSignatureProviderOpts = { - /** The `DidUri` of the DIP subject that is performing the cross-chain operation. */ - didUri: DidUri - /** The list of `Signers` to use to sign the cross-chain payload. */ - signer: SignExtrinsicCallback - /** The `SignatureVerificationRelationship` to use from the provided DID Document to sign the cross-chain payload. */ - keyRelationship: VerificationKeyRelationship -} -/** - * The Consumer options object provided when generating a DIP DID signature. - */ -export type DipDidSignatureConsumerOpts = { - /** The runtime definition of an `AccountId`. */ - accountIdRuntimeType: string - /** The `ApiPromise` instance. */ - api: ApiPromise - /** The runtime definition of a `BlockNumber`. */ - blockNumberRuntimeType: string - /** The `Call` to DID-authorize. */ - call: Call - /** The runtime definition of the `IdentityDetails`. */ - identityDetailsRuntimeType: string - /** The address of the submitter account on the consumer chain. */ - submitterAddress: KeyringPair["address"] - /** The block number until which the DID signature is to be considered fresh. If not provided, the latest best block number + an offset of 50 is used. */ - validUntil?: BN - /** The genesis hash to use for the DID signature. If not provided, it is retrieved at runtime. */ - genesisHash?: Hash -} -/** - * The options object provided when generating a DIP DID signature. - */ -export type DipDidSignatureOpts = { - consumer: DipDidSignatureConsumerOpts - provider: DipDidSignatureProviderOpts -} -/** - * The response object for DIP DID signature. - */ -export type DipDidSignatureRes = { - validUntil: BN - signature: Uint8Array - type: VerificationKeyType -} -/** - * Generate a DID signature to be used in conjunction with a DIP proof to DID-authorize a cross-chain operation. - * - * @param params The signature generation parameters. - - * @returns The generated DIP proof. - */ -export async function generateDipDidSignature({ - provider: { didUri, signer, keyRelationship }, - consumer: { - accountIdRuntimeType, - api, - blockNumberRuntimeType, - call, - identityDetailsRuntimeType, - submitterAddress, - // Optional - validUntil, - genesisHash, - }, -}: DipDidSignatureOpts): Promise { - const blockNumber: BN = - validUntil ?? - (await api.query.system.number()) - .toBn() - .add(defaultValues.validUntilOffset) - const genesis = genesisHash ?? (await api.query.system.blockHash(0)) - const identityDetails = ( - await api.query.dipConsumer.identityEntries>(toChain(didUri)) - ).unwrapOr(api.createType(identityDetailsRuntimeType, null)) - - const signaturePayload = api - .createType( - `(Call, ${identityDetailsRuntimeType}, ${accountIdRuntimeType}, ${blockNumberRuntimeType}, Hash)`, - [call, identityDetails, submitterAddress, blockNumber, genesis], - ) - .toU8a() - const { signature, keyType } = await signer({ - data: signaturePayload, - did: didUri, - keyRelationship, - }) - return { - validUntil: blockNumber, - signature, - type: keyType, - } -} diff --git a/tests/dip-provider-template-dip-consumer-template/develop.test.ts b/tests/dip-provider-template-dip-consumer-template/develop.test.ts index 56f9993..4ec056a 100644 --- a/tests/dip-provider-template-dip-consumer-template/develop.test.ts +++ b/tests/dip-provider-template-dip-consumer-template/develop.test.ts @@ -15,7 +15,12 @@ import dotenv from "dotenv" import { beforeAll, describe, it, expect } from "vitest" import type { GetStoreTxSignCallback, Web3Name } from "@kiltprotocol/did" -import type { DipSiblingProofInput } from "@kiltprotocol/dip-sdk" +import type { + DipSiblingBaseProofInput, + TimeBoundDidSignatureConsumerOpts, + TimeBoundDidSignatureOpts, + TimeBoundDidSignatureProviderOpts, +} from "@kiltprotocol/dip-sdk" import type { DidDocument, KiltAddress, @@ -25,18 +30,14 @@ import type { Option } from "@polkadot/types/codec" import type { Call } from "@polkadot/types/interfaces" import type { Codec } from "@polkadot/types/types" -import { - createProviderApi, - signAndSubmitTx, - withCrossModuleSystemImport, -} from "../utils.js" +import { signAndSubmitTx, withCrossModuleSystemImport } from "../utils.js" dotenv.config({ path: "tests/dip-provider-template-dip-consumer-template/.env.develop.test", }) const baseConfig: Pick< - DipSiblingProofInput, + TimeBoundDidSignatureConsumerOpts, | "accountIdRuntimeType" | "blockNumberRuntimeType" | "identityDetailsRuntimeType" @@ -62,14 +63,14 @@ describe("V0", () => { // beforeAll let v0Config: typeof baseConfig & Pick< - DipSiblingProofInput, - "consumerApi" | "proofVersion" | "providerApi" | "relayApi" - > + DipSiblingBaseProofInput, + "proofVersion" | "providerApi" | "relayApi" + > & { consumerApi: TimeBoundDidSignatureConsumerOpts["api"] } beforeAll(async () => { const [relayApi, providerApi, consumerApi] = await Promise.all([ ApiPromise.create({ provider: new WsProvider(relayAddress) }), - createProviderApi(providerAddress), + Kilt.connect(providerAddress), ApiPromise.create({ provider: new WsProvider(consumerAddress) }), ]) Kilt.ConfigService.set({ api: providerApi }) @@ -92,14 +93,11 @@ describe("V0", () => { let lastTestSetupProviderBlockNumber: BN let testConfig: typeof v0Config & Pick< - DipSiblingProofInput, - | "didUri" - | "signer" - | "keyIds" - | "keyRelationship" - | "includeWeb3Name" - | "submitterAddress" - > + DipSiblingBaseProofInput, + "didUri" | "keyIds" | "includeWeb3Name" | "linkedAccounts" + > & + Pick & + Pick beforeAll(async () => { const { providerApi, consumerApi } = v0Config @@ -200,16 +198,32 @@ describe("V0", () => { it("Successful posts on the consumer's PostIt pallet using by default the latest provider finalized block", async () => { const { consumerApi } = testConfig const postText = "Hello, world!" - const config: DipSiblingProofInput = { + const call = consumerApi.tx.postIt.post(postText).method as Call + const config: DipSiblingBaseProofInput & TimeBoundDidSignatureOpts = { ...testConfig, - call: consumerApi.tx.postIt.post(postText).method as Call, + provider: testConfig, + consumer: { ...testConfig, api: consumerApi, call }, } + const baseDipProof = await DipSdk.generateDipSiblingBaseProof(config) + const crossChainDidSignature = + await DipSdk.dipProof.extensions.timeBoundDidSignature.generateDidSignature( + config, + ) + + const dipSubmittable = DipSdk.generateDipSubmittableExtrinsic({ + additionalProofElements: + DipSdk.dipProof.extensions.timeBoundDidSignature.toChain( + crossChainDidSignature, + ), + api: consumerApi, + baseDipProof, + call, + didUri: did.uri, + }) - const crossChainTx = - await DipSdk.generateDipAuthorizedTxForSibling(config) const { status } = await signAndSubmitTx( consumerApi, - crossChainTx, + dipSubmittable, submitterKeypair, ) expect( @@ -224,7 +238,7 @@ describe("V0", () => { consumerApi .createType( `(${ - config.blockNumberRuntimeType as string + config.consumer.blockNumberRuntimeType as string }, ${web3NameRuntimeType}, Bytes)`, [blockNumber, web3Name, postText], ) @@ -241,18 +255,35 @@ describe("V0", () => { it("Successful posts on the consumer's PostIt pallet using the same block as before", async () => { const { consumerApi } = testConfig const postText = "Hello, world!" - const config: DipSiblingProofInput = { + const call = consumerApi.tx.postIt.post(postText).method as Call + const config: DipSiblingBaseProofInput & TimeBoundDidSignatureOpts = { ...testConfig, - call: consumerApi.tx.postIt.post(postText).method as Call, // Set explicit block number for the DIP proof providerBlockHeight: lastTestSetupProviderBlockNumber, + provider: testConfig, + consumer: { ...testConfig, api: consumerApi, call }, } - const crossChainTx = - await DipSdk.generateDipAuthorizedTxForSibling(config) + const baseDipProof = await DipSdk.generateDipSiblingBaseProof(config) + const crossChainDidSignature = + await DipSdk.dipProof.extensions.timeBoundDidSignature.generateDidSignature( + config, + ) + + const dipSubmittable = DipSdk.generateDipSubmittableExtrinsic({ + additionalProofElements: + DipSdk.dipProof.extensions.timeBoundDidSignature.toChain( + crossChainDidSignature, + ), + api: consumerApi, + baseDipProof, + call, + didUri: did.uri, + }) + const { status } = await signAndSubmitTx( consumerApi, - crossChainTx, + dipSubmittable, submitterKeypair, ) expect( @@ -267,7 +298,7 @@ describe("V0", () => { consumerApi .createType( `(${ - config.blockNumberRuntimeType as string + config.consumer.blockNumberRuntimeType as string }, ${web3NameRuntimeType}, Bytes)`, [blockNumber, web3Name, postText], ) diff --git a/tests/peregrine-dip-consumer-template/develop-zombienet.toml b/tests/peregrine-dip-consumer-template/develop-zombienet.toml index d470bb6..8631e09 100644 --- a/tests/peregrine-dip-consumer-template/develop-zombienet.toml +++ b/tests/peregrine-dip-consumer-template/develop-zombienet.toml @@ -3,6 +3,7 @@ enable_tracing = false provider = "kubernetes" # 18000 seconds -> 300 minutes -> 5 hours timeout = 18000 +node_verifier = "None" # Env variables: # * RELAY_IMAGE: Docker image for relaychain nodes diff --git a/tests/peregrine-dip-consumer-template/develop.test.ts b/tests/peregrine-dip-consumer-template/develop.test.ts index b829b46..53b4c19 100644 --- a/tests/peregrine-dip-consumer-template/develop.test.ts +++ b/tests/peregrine-dip-consumer-template/develop.test.ts @@ -8,14 +8,19 @@ import { setTimeout } from "timers/promises" import * as Kilt from "@kiltprotocol/sdk-js" -import { ApiPromise, Keyring, WsProvider } from "@polkadot/api" +import { ApiPromise, WsProvider } from "@polkadot/api" import { BN } from "@polkadot/util" import { blake2AsHex } from "@polkadot/util-crypto" import dotenv from "dotenv" import { beforeAll, describe, it, expect } from "vitest" import type { GetStoreTxSignCallback, Web3Name } from "@kiltprotocol/did" -import type { DipSiblingProofInput } from "@kiltprotocol/dip-sdk" +import type { + DipSiblingBaseProofInput, + TimeBoundDidSignatureConsumerOpts, + TimeBoundDidSignatureOpts, + TimeBoundDidSignatureProviderOpts, +} from "@kiltprotocol/dip-sdk" import type { DidDocument, KiltAddress, @@ -32,7 +37,7 @@ dotenv.config({ }) const baseConfig: Pick< - DipSiblingProofInput, + TimeBoundDidSignatureConsumerOpts, | "accountIdRuntimeType" | "blockNumberRuntimeType" | "identityDetailsRuntimeType" @@ -58,9 +63,9 @@ describe("V0", () => { // beforeAll let v0Config: typeof baseConfig & Pick< - DipSiblingProofInput, - "consumerApi" | "proofVersion" | "providerApi" | "relayApi" - > + DipSiblingBaseProofInput, + "proofVersion" | "providerApi" | "relayApi" + > & { consumerApi: TimeBoundDidSignatureConsumerOpts["api"] } beforeAll(async () => { const [relayApi, providerApi, consumerApi] = await Promise.all([ @@ -88,15 +93,11 @@ describe("V0", () => { let lastTestSetupProviderBlockNumber: BN let testConfig: typeof v0Config & Pick< - DipSiblingProofInput, - | "didUri" - | "signer" - | "keyIds" - | "keyRelationship" - | "includeWeb3Name" - | "submitterAddress" - | "linkedAccounts" - > + DipSiblingBaseProofInput, + "didUri" | "keyIds" | "includeWeb3Name" | "linkedAccounts" + > & + Pick & + Pick beforeAll(async () => { const { providerApi, consumerApi } = v0Config @@ -158,7 +159,7 @@ describe("V0", () => { newSubmitterKeypair.address as KiltAddress, { txCounter: new BN(1) }, ) - const newAttestationKey = new Keyring({ + const newAttestationKey = new Kilt.Utils.Keyring({ type: "ed25519", }).addFromMnemonic(Kilt.Utils.Crypto.mnemonicGenerate()) const newAttestationKeyTx = (() => { @@ -176,9 +177,9 @@ describe("V0", () => { newSubmitterKeypair.address as KiltAddress, { txCounter: new BN(2) }, ) - const newDelegationKey = new Keyring({ type: "ed25519" }).addFromMnemonic( - Kilt.Utils.Crypto.mnemonicGenerate(), - ) + const newDelegationKey = new Kilt.Utils.Keyring({ + type: "ed25519", + }).addFromMnemonic(Kilt.Utils.Crypto.mnemonicGenerate()) const newDelegationKeyTx = (() => { return providerApi.tx.did.setDelegationKey( Kilt.Did.publicKeyToChain({ @@ -195,7 +196,7 @@ describe("V0", () => { { txCounter: new BN(3) }, ) const linkedAccounts = [...Array(10)].map(() => - new Keyring({ type: "ed25519" }).addFromMnemonic( + new Kilt.Utils.Keyring({ type: "ed25519" }).addFromMnemonic( Kilt.Utils.Crypto.mnemonicGenerate(), ), ) @@ -288,16 +289,32 @@ describe("V0", () => { it("Successful posts on the consumer's PostIt pallet using by default the latest provider finalized block", async () => { const { consumerApi } = testConfig const postText = "Hello, world!" - const config: DipSiblingProofInput = { + const call = consumerApi.tx.postIt.post(postText).method as Call + const config: DipSiblingBaseProofInput & TimeBoundDidSignatureOpts = { ...testConfig, - call: consumerApi.tx.postIt.post(postText).method as Call, + provider: testConfig, + consumer: { ...testConfig, api: consumerApi, call }, } + const baseDipProof = await DipSdk.generateDipSiblingBaseProof(config) + const crossChainDidSignature = + await DipSdk.dipProof.extensions.timeBoundDidSignature.generateDidSignature( + config, + ) + + const dipSubmittable = DipSdk.generateDipSubmittableExtrinsic({ + additionalProofElements: + DipSdk.dipProof.extensions.timeBoundDidSignature.toChain( + crossChainDidSignature, + ), + api: consumerApi, + baseDipProof, + call, + didUri: did.uri, + }) - const crossChainTx = - await DipSdk.generateDipAuthorizedTxForSibling(config) const { status } = await signAndSubmitTx( consumerApi, - crossChainTx, + dipSubmittable, submitterKeypair, ) expect( @@ -312,7 +329,7 @@ describe("V0", () => { consumerApi .createType( `(${ - config.blockNumberRuntimeType as string + config.consumer.blockNumberRuntimeType as string }, ${web3NameRuntimeType}, Bytes)`, [blockNumber, web3Name, postText], ) @@ -329,18 +346,35 @@ describe("V0", () => { it("Successful posts on the consumer's PostIt pallet using the same block as before", async () => { const { consumerApi } = testConfig const postText = "Hello, world!" - const config: DipSiblingProofInput = { + const call = consumerApi.tx.postIt.post(postText).method as Call + const config: DipSiblingBaseProofInput & TimeBoundDidSignatureOpts = { ...testConfig, - call: consumerApi.tx.postIt.post(postText).method as Call, // Set explicit block number for the DIP proof providerBlockHeight: lastTestSetupProviderBlockNumber, + provider: testConfig, + consumer: { ...testConfig, api: consumerApi, call }, } - const crossChainTx = - await DipSdk.generateDipAuthorizedTxForSibling(config) + const baseDipProof = await DipSdk.generateDipSiblingBaseProof(config) + const crossChainDidSignature = + await DipSdk.dipProof.extensions.timeBoundDidSignature.generateDidSignature( + config, + ) + + const dipSubmittable = DipSdk.generateDipSubmittableExtrinsic({ + additionalProofElements: + DipSdk.dipProof.extensions.timeBoundDidSignature.toChain( + crossChainDidSignature, + ), + api: consumerApi, + baseDipProof, + call, + didUri: did.uri, + }) + const { status } = await signAndSubmitTx( consumerApi, - crossChainTx, + dipSubmittable, submitterKeypair, ) expect( @@ -355,7 +389,7 @@ describe("V0", () => { consumerApi .createType( `(${ - config.blockNumberRuntimeType as string + config.consumer.blockNumberRuntimeType as string }, ${web3NameRuntimeType}, Bytes)`, [blockNumber, web3Name, postText], ) diff --git a/tests/utils.ts b/tests/utils.ts index f095b48..a6dbc96 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -6,104 +6,15 @@ */ import * as Kilt from "@kiltprotocol/sdk-js" -import { didCalls, types } from "@kiltprotocol/type-definitions" -import { ApiPromise, SubmittableResult, WsProvider } from "@polkadot/api" +import { SubmittableResult } from "@polkadot/api" import { describe } from "vitest" import type { KeyringPair, SubmittableExtrinsic } from "@kiltprotocol/types" -import type { - AnyNumber, - ISubmittableResult, - DefinitionsCall, - RegistryTypes, -} from "@polkadot/types/types" - -import { dipProviderCalls } from "../src/runtime.js" - -const dipProviderTemplateRuntimeCalls: DefinitionsCall = { - ...dipProviderCalls, - ...didCalls, -} -const dipTypes: RegistryTypes = { - ...types, - IdentityCommitmentVersion: "u16", - // DipProvider state_call - DipProofRequest: { - identifier: "AccountId32", - version: "IdentityCommitmentVersion", - keys: "Vec", - accounts: "Vec", - shouldIncludeWeb3Name: "bool", - }, - CompleteMerkleProof: { - root: "MerkleRoot", - proof: "MerkleProof", - }, - MerkleRoot: "Hash", - MerkleProof: { - blinded: "BlindedLeaves", - revealed: "RevealedLeaves", - }, - BlindedLeaves: "Vec", - BlindedValue: "Bytes", - RevealedLeaves: "Vec", - RevealedLeaf: { - _enum: { - DidKey: "(DidKeyMerkleKey, DidKeyMerkleValue)", - Web3Name: "(Web3NameMerkleKey, Web3NameMerkleValue)", - LinkedAccount: "(LinkedAccountMerkleKey, LinkedAccountMerkleValue)", - }, - }, - DidKeyMerkleKey: "(KeyId, KeyRelationship)", - KeyId: "Hash", - KeyRelationship: { - _enum: { - Encryption: "Null", - Verification: "VerificationRelationship", - }, - }, - VerificationRelationship: { - _enum: [ - "Authentication", - "CapabilityDelegation", - "CapabilityInvocation", - "AssertionMethod", - ], - }, - DidKeyMerkleValue: "DidDidDetailsDidPublicKeyDetails", - Web3NameMerkleKey: "Text", - Web3NameMerkleValue: "BlockNumber", - LinkedAccountMerkleKey: "PalletDidLookupLinkableAccountLinkableAccountId", - LinkedAccountMerkleValue: "Null", - RuntimeApiDipProofError: { - _enum: { - IdentityProvider: "LinkedDidIdentityProviderError", - MerkleProof: "DidMerkleProofError", - }, - }, - LinkedDidIdentityProviderError: { - _enum: ["DidNotFound", "DidDeleted", "Internal"], - }, - DidIdentityProviderError: { - _enum: ["DidNotFound", "Internal"], - }, - DidMerkleProofError: { - _enum: [ - "UnsupportedVersion", - "KeyNotFound", - "LinkedAccountNotFound", - "Web3NameNotFound", - "Internal", - ], - }, -} +import type { ApiPromise } from "@polkadot/api" +import type { AnyNumber, ISubmittableResult } from "@polkadot/types/types" export async function createProviderApi(address: string): Promise { - return ApiPromise.create({ - provider: new WsProvider(address), - runtime: dipProviderTemplateRuntimeCalls, - types: dipTypes, - }) + return Kilt.connect(address) } // Taken from the KILT SDK: https://github.com/KILTprotocol/sdk-js/blob/c4ab492812d19169532a399b57dd1bd013a61570/packages/chain-helpers/src/blockchain/Blockchain.ts#L179