From da2b52ffddcfbb977bfcd0e88828999a22c18180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aybars=20G=C3=B6ktu=C4=9F=20Ayan?= Date: Tue, 19 Nov 2024 20:20:43 +0300 Subject: [PATCH 01/13] feat: revoke in issuer --- packages/credentials/src/issuer.ts | 71 ++++++++++++++++++++++++++++++ tests/bundle/bundle-test.ts | 18 ++++++++ 2 files changed, 89 insertions(+) diff --git a/packages/credentials/src/issuer.ts b/packages/credentials/src/issuer.ts index 712b40442..853a5dad0 100644 --- a/packages/credentials/src/issuer.ts +++ b/packages/credentials/src/issuer.ts @@ -15,6 +15,16 @@ import type { CTypeLoader } from './ctype/index.js' import type { IssuerOptions, SubmitOverride } from './interfaces.js' export type { IssuerOptions } +import * as Kilt from '@kiltprotocol/sdk-js' +import * as KiltChain from '@kiltprotocol/chain-helpers' +import type { + KiltAddress, + SignerInterface, + KeyringPair, + DidUrl, +} from '@kiltprotocol/types' +import {authorizeTx} from "@kiltprotocol/did" +import { base58Decode } from '@polkadot/util-crypto' /** * Creates a new credential document as a basis for issuing a credential. @@ -134,3 +144,64 @@ export async function issue({ ) } } + +/** +* Revokes a Kilt credential on the blockchain, making it invalid. +* +* @param attester The DID URL of the attester revoking the credential +* @param submitterAccount The account that will submit and pay for the transaction +* @param signCallback Callback function to sign the DID authorization +* @param credential The Verifiable Credential to be revoked +* @returns An object containing: +* - success: Boolean indicating if revocation was successful +* - error?: Array of error messages if revocation failed +* - info: Object containing blockchain transaction details: +* - blockNumber?: Block number where transaction was included +* - blockHash?: Hash of the block +* - transactionHash?: Hash of the transaction +*/ +export async function revoke( + attester: DidUrl, + submitterAccount: KeyringPair, + signCallback: any, + credential: VerifiableCredential, + ): Promise<{success: boolean, error?: Array, info: {blockNumber?: string, blockHash?: string, transactionHash?: string}}> { + try { + const api = Kilt.ConfigService.get('api') + const rootHash = credential.id?.split(':').pop()! + const decodedroothash = base58Decode(rootHash); + + const revokeTx = api.tx.attestation.revoke(decodedroothash, null) as any + const [submitter] = (await Kilt.getSignersForKeypair({ + keypair: submitterAccount, + type: 'Ed25519', + })) as Array> + + const authorizedTx = await authorizeTx( + attester, + revokeTx, + signCallback, + submitter.id + ) + + const response = await KiltChain.Blockchain.signAndSubmitTx(authorizedTx, submitterAccount) as Object + const responseObj = JSON.parse(JSON.stringify(response)); + + return { + success: true, + info: { + blockNumber: responseObj.blockNumber, + blockHash: responseObj.status.finalized, + transactionHash: responseObj.txHash + } + } + + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + return { + success: false, + error: [errorMessage], + info: {} + } + } +} \ No newline at end of file diff --git a/tests/bundle/bundle-test.ts b/tests/bundle/bundle-test.ts index 43ca10776..6e2a912b4 100644 --- a/tests/bundle/bundle-test.ts +++ b/tests/bundle/bundle-test.ts @@ -370,6 +370,24 @@ async function runAll() { console.log('Did deactivated') +// ┏━━━━━━━━━━━━━━━━━━┓ +// ┃ Revoke credential┃ +// ┗━━━━━━━━━━━━━━━━━━┛ +// +// Revoke a previously issued credential on chain +const revokeCredentialResult = await Kilt.Issuer.revoke( + didDocument.id, + submitter, + signers, + credential + ) + + if (!revokeCredentialResult.success) { + throw new Error(`revoke credential failed: ${revokeCredentialResult.error?.join(', ')}`) + } + + console.log('credential revoked') + // Release the connection to the blockchain. await api.disconnect() From ccac9b90e1ceac14a1c26d443d3fcef994745055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aybars=20G=C3=B6ktu=C4=9F=20Ayan?= Date: Tue, 3 Dec 2024 13:20:16 +0300 Subject: [PATCH 02/13] fix: review notes --- packages/credentials/src/issuer.ts | 151 ++++++++++++++++++----------- tests/bundle/bundle-test.ts | 36 +++---- 2 files changed, 115 insertions(+), 72 deletions(-) diff --git a/packages/credentials/src/issuer.ts b/packages/credentials/src/issuer.ts index 853a5dad0..d2555d3f0 100644 --- a/packages/credentials/src/issuer.ts +++ b/packages/credentials/src/issuer.ts @@ -5,26 +5,28 @@ * found in the LICENSE file in the root directory of this source tree. */ -import type { Did, ICType, IClaimContents } from '@kiltprotocol/types' - -import { SDKErrors } from '@kiltprotocol/utils' -import { KiltAttestationProofV1, KiltCredentialV1 } from './V1/index.js' -import type { UnsignedVc, VerifiableCredential } from './V1/types.js' -import type { CTypeLoader } from './ctype/index.js' +import { SDKErrors, Signers } from '@kiltprotocol/utils' // eslint-disable-next-line @typescript-eslint/no-unused-vars -import type { IssuerOptions, SubmitOverride } from './interfaces.js' - -export type { IssuerOptions } -import * as Kilt from '@kiltprotocol/sdk-js' import * as KiltChain from '@kiltprotocol/chain-helpers' +// import * as Kilt from '@kiltprotocol/sdk-js' +// import * as KiltChain from '@kiltprotocol/chain-helpers' import type { KiltAddress, SignerInterface, - KeyringPair, - DidUrl, + Did, + ICType, + IClaimContents, + MultibaseKeyPair, } from '@kiltprotocol/types' -import {authorizeTx} from "@kiltprotocol/did" +import { authorizeTx } from '@kiltprotocol/did' import { base58Decode } from '@polkadot/util-crypto' +import { ConfigService } from '@kiltprotocol/config' +import type { IssuerOptions, SubmitOverride } from './interfaces.js' +import type { CTypeLoader } from './ctype/index.js' +import type { UnsignedVc, VerifiableCredential } from './V1/types.js' +import { KiltAttestationProofV1, KiltCredentialV1 } from './V1/index.js' + +export type { IssuerOptions } /** * Creates a new credential document as a basis for issuing a credential. @@ -145,63 +147,104 @@ export async function issue({ } } +interface RevokeResult { + success: boolean + error?: string[] + info: { + blockNumber?: string + blockHash?: string + transactionHash?: string + } +} + +interface BlockchainResponse { + blockNumber: string + status: { + finalized: string + } + txHash: string +} + /** -* Revokes a Kilt credential on the blockchain, making it invalid. -* -* @param attester The DID URL of the attester revoking the credential -* @param submitterAccount The account that will submit and pay for the transaction -* @param signCallback Callback function to sign the DID authorization -* @param credential The Verifiable Credential to be revoked -* @returns An object containing: -* - success: Boolean indicating if revocation was successful -* - error?: Array of error messages if revocation failed -* - info: Object containing blockchain transaction details: -* - blockNumber?: Block number where transaction was included -* - blockHash?: Hash of the block -* - transactionHash?: Hash of the transaction -*/ + * Revokes a Kilt credential on the blockchain, making it invalid. + * + * @param params Named parameters for the revocation process. + * @param params.issuer Interfaces for interacting with the issuer identity. + * @param params.issuer.didDocument The DID Document of the issuer revoking the credential. + * @param params.issuer.signers Array of signer interfaces for credential authorization. + * @param params.issuer.submitter The submitter can be one of: + * - A MultibaseKeyPair for signing transactions + * - A Ed25519 type keypair for blockchain interactions + * The submitter will be used to cover transaction fees and blockchain operations. + * @param params.credential The Verifiable Credential to be revoked. Must contain a valid credential ID. + * @param issuer + * @param credential + * @returns An object containing: + * - success: Boolean indicating if revocation was successful + * - error?: Array of error messages if revocation failed + * - info: Object containing blockchain transaction details: + * - blockNumber?: The block number where revocation was included + * - blockHash?: The hash of the finalized block + * - transactionHash?: The hash of the revocation transaction. + * @throws Will return error response if: + * - Credential ID is invalid or cannot be decoded + * - DID authorization fails + * - Transaction signing or submission fails. + */ export async function revoke( - attester: DidUrl, - submitterAccount: KeyringPair, - signCallback: any, - credential: VerifiableCredential, - ): Promise<{success: boolean, error?: Array, info: {blockNumber?: string, blockHash?: string, transactionHash?: string}}> { + issuer: IssuerOptions, + credential: VerifiableCredential +): Promise { try { - const api = Kilt.ConfigService.get('api') - const rootHash = credential.id?.split(':').pop()! - const decodedroothash = base58Decode(rootHash); - + if (!credential.id) { + throw new Error('Credential ID is required for revocation') + } + + const rootHash = credential.id.split(':').pop() + if (!rootHash) { + throw new Error('Invalid credential ID format') + } + + const decodedroothash = base58Decode(rootHash) + const { didDocument, signers, submitter } = issuer + const api = ConfigService.get('api') + + console.log(didDocument) + const revokeTx = api.tx.attestation.revoke(decodedroothash, null) as any - const [submitter] = (await Kilt.getSignersForKeypair({ - keypair: submitterAccount, - type: 'Ed25519', + const [Txsubmitter] = (await Signers.getSignersForKeypair({ + keypair: submitter as MultibaseKeyPair, + type: 'Ed25519', })) as Array> - const authorizedTx = await authorizeTx( - attester, + didDocument, revokeTx, - signCallback, - submitter.id + signers as SignerInterface[], + Txsubmitter.id ) - - const response = await KiltChain.Blockchain.signAndSubmitTx(authorizedTx, submitterAccount) as Object - const responseObj = JSON.parse(JSON.stringify(response)); - + + const response = (await KiltChain.Blockchain.signAndSubmitTx( + authorizedTx, + Txsubmitter + )) as unknown as BlockchainResponse + + const responseObj = JSON.parse(JSON.stringify(response)) + return { success: true, info: { blockNumber: responseObj.blockNumber, blockHash: responseObj.status.finalized, - transactionHash: responseObj.txHash - } + transactionHash: responseObj.txHash, + }, } - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + const errorMessage = + error instanceof Error ? error.message : 'Unknown error occurred' return { success: false, error: [errorMessage], - info: {} + info: {}, } - } -} \ No newline at end of file + } +} diff --git a/tests/bundle/bundle-test.ts b/tests/bundle/bundle-test.ts index 6e2a912b4..302a45344 100644 --- a/tests/bundle/bundle-test.ts +++ b/tests/bundle/bundle-test.ts @@ -370,24 +370,24 @@ async function runAll() { console.log('Did deactivated') -// ┏━━━━━━━━━━━━━━━━━━┓ -// ┃ Revoke credential┃ -// ┗━━━━━━━━━━━━━━━━━━┛ -// -// Revoke a previously issued credential on chain -const revokeCredentialResult = await Kilt.Issuer.revoke( - didDocument.id, - submitter, - signers, - credential - ) - - if (!revokeCredentialResult.success) { - throw new Error(`revoke credential failed: ${revokeCredentialResult.error?.join(', ')}`) - } - - console.log('credential revoked') - + // ┏━━━━━━━━━━━━━━━━━━┓ + // ┃ Revoke credential┃ + // ┗━━━━━━━━━━━━━━━━━━┛ + // + // Revoke a previously issued credential on chain + const revokeCredentialResult = await Kilt.Issuer.revoke( + didDocument.id, + credential + ) + + if (!revokeCredentialResult.success) { + throw new Error( + `revoke credential failed: ${revokeCredentialResult.error?.join(', ')}` + ) + } + + console.log('credential revoked') + // Release the connection to the blockchain. await api.disconnect() From d7562d1eb5e1b552e4558c50a9f982fa1108d97c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aybars=20G=C3=B6ktu=C4=9F=20Ayan?= Date: Tue, 3 Dec 2024 13:25:53 +0300 Subject: [PATCH 03/13] fix: merge-fix --- packages/credentials/src/issuer.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/credentials/src/issuer.ts b/packages/credentials/src/issuer.ts index d2555d3f0..2f8ca5f6e 100644 --- a/packages/credentials/src/issuer.ts +++ b/packages/credentials/src/issuer.ts @@ -7,9 +7,7 @@ import { SDKErrors, Signers } from '@kiltprotocol/utils' // eslint-disable-next-line @typescript-eslint/no-unused-vars -import * as KiltChain from '@kiltprotocol/chain-helpers' -// import * as Kilt from '@kiltprotocol/sdk-js' -// import * as KiltChain from '@kiltprotocol/chain-helpers' +import { Blockchain } from '@kiltprotocol/chain-helpers' import type { KiltAddress, SignerInterface, @@ -223,7 +221,7 @@ export async function revoke( Txsubmitter.id ) - const response = (await KiltChain.Blockchain.signAndSubmitTx( + const response = (await Blockchain.signAndSubmitTx( authorizedTx, Txsubmitter )) as unknown as BlockchainResponse From 110a5cc482545b61bf5e514493616e2ffd3aaa69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aybars=20G=C3=B6ktu=C4=9F=20Ayan?= Date: Thu, 19 Dec 2024 12:26:09 +0300 Subject: [PATCH 04/13] fix: relocation of revoke func --- .../src/V1/KiltRevocationStatusV1.ts | 120 +++++++++++++- packages/credentials/src/issuer.ts | 155 ++++++------------ 2 files changed, 162 insertions(+), 113 deletions(-) diff --git a/packages/credentials/src/V1/KiltRevocationStatusV1.ts b/packages/credentials/src/V1/KiltRevocationStatusV1.ts index 16130a9d7..237781418 100644 --- a/packages/credentials/src/V1/KiltRevocationStatusV1.ts +++ b/packages/credentials/src/V1/KiltRevocationStatusV1.ts @@ -9,23 +9,133 @@ import { u8aEq, u8aToHex, u8aToU8a } from '@polkadot/util' import { base58Decode, base58Encode } from '@polkadot/util-crypto' import type { ApiPromise } from '@polkadot/api' import type { U8aLike } from '@polkadot/util/types' - +import { authorizeTx } from '@kiltprotocol/did' import { ConfigService } from '@kiltprotocol/config' -import type { Caip2ChainId } from '@kiltprotocol/types' -import { Caip2, SDKErrors } from '@kiltprotocol/utils' - +import type { + Caip2ChainId, + KiltAddress, + SignerInterface, + MultibaseKeyPair, +} from '@kiltprotocol/types' +import { Caip2, SDKErrors, Signers } from '@kiltprotocol/utils' +import { Blockchain } from '@kiltprotocol/chain-helpers' import * as CType from '../ctype/index.js' import * as Attestation from '../attestation/index.js' import { assertMatchingConnection, getDelegationNodeIdForCredential, } from './common.js' -import type { KiltCredentialV1, KiltRevocationStatusV1 } from './types.js' +import type { IssuerOptions } from '../interfaces.js' +import type { + KiltCredentialV1, + KiltRevocationStatusV1, + VerifiableCredential, +} from './types.js' export type Interface = KiltRevocationStatusV1 export const STATUS_TYPE = 'KiltRevocationStatusV1' +interface RevokeResult { + success: boolean + error?: string[] + info: { + blockNumber?: string + blockHash?: string + transactionHash?: string + } +} + +interface BlockchainResponse { + blockNumber: string + status: { + finalized: string + } + txHash: string +} + +/** + * Revokes a Kilt credential on the blockchain, making it invalid. + * + * @param params Named parameters for the revocation process. + * @param params.issuer Interfaces for interacting with the issuer identity. + * @param params.issuer.didDocument The DID Document of the issuer revoking the credential. + * @param params.issuer.signers Array of signer interfaces for credential authorization. + * @param params.issuer.submitter The submitter can be one of: + * - A MultibaseKeyPair for signing transactions + * - A Ed25519 type keypair for blockchain interactions + * The submitter will be used to cover transaction fees and blockchain operations. + * @param params.credential The Verifiable Credential to be revoked. Must contain a valid credential ID. + * @param issuer + * @param credential + * @returns An object containing: + * - success: Boolean indicating if revocation was successful + * - error?: Array of error messages if revocation failed + * - info: Object containing blockchain transaction details: + * - blockNumber?: The block number where revocation was included + * - blockHash?: The hash of the finalized block + * - transactionHash?: The hash of the revocation transaction. + * @throws Will return error response if: + * - Credential ID is invalid or cannot be decoded + * - DID authorization fails + * - Transaction signing or submission fails. + */ +export async function revoke( + issuer: IssuerOptions, + credential: VerifiableCredential +): Promise { + try { + if (!credential.id) { + throw new Error('Credential ID is required for revocation') + } + + const rootHash = credential.id.split(':').pop() + if (!rootHash) { + throw new Error('Invalid credential ID format') + } + + const decodedroothash = base58Decode(rootHash) + const { didDocument, signers, submitter } = issuer + const api = ConfigService.get('api') + + const revokeTx = api.tx.attestation.revoke(decodedroothash, null) as any + const [Txsubmitter] = (await Signers.getSignersForKeypair({ + keypair: submitter as MultibaseKeyPair, + type: 'Ed25519', + })) as Array> + const authorizedTx = await authorizeTx( + didDocument, + revokeTx, + signers as SignerInterface[], + Txsubmitter.id + ) + + const response = (await Blockchain.signAndSubmitTx( + authorizedTx, + Txsubmitter + )) as unknown as BlockchainResponse + + const responseObj = JSON.parse(JSON.stringify(response)) + + return { + success: true, + info: { + blockNumber: responseObj.blockNumber, + blockHash: responseObj.status.finalized, + transactionHash: responseObj.txHash, + }, + } + } catch (error: unknown) { + const errorMessage = + error instanceof Error ? error.message : 'Unknown error occurred' + return { + success: false, + error: [errorMessage], + info: {}, + } + } +} + /** * Check attestation and revocation status of a credential at the latest block available. * diff --git a/packages/credentials/src/issuer.ts b/packages/credentials/src/issuer.ts index 2f8ca5f6e..c18a13999 100644 --- a/packages/credentials/src/issuer.ts +++ b/packages/credentials/src/issuer.ts @@ -5,27 +5,30 @@ * found in the LICENSE file in the root directory of this source tree. */ -import { SDKErrors, Signers } from '@kiltprotocol/utils' +import { SDKErrors } from '@kiltprotocol/utils' // eslint-disable-next-line @typescript-eslint/no-unused-vars -import { Blockchain } from '@kiltprotocol/chain-helpers' -import type { - KiltAddress, - SignerInterface, - Did, - ICType, - IClaimContents, - MultibaseKeyPair, -} from '@kiltprotocol/types' -import { authorizeTx } from '@kiltprotocol/did' -import { base58Decode } from '@polkadot/util-crypto' -import { ConfigService } from '@kiltprotocol/config' -import type { IssuerOptions, SubmitOverride } from './interfaces.js' +import type { Did, ICType, IClaimContents } from '@kiltprotocol/types' +import type { IssuerOptions } from './interfaces.js' import type { CTypeLoader } from './ctype/index.js' import type { UnsignedVc, VerifiableCredential } from './V1/types.js' -import { KiltAttestationProofV1, KiltCredentialV1 } from './V1/index.js' +import { + KiltAttestationProofV1, + KiltCredentialV1, + KiltRevocationStatusV1, +} from './V1/index.js' export type { IssuerOptions } +interface RevokeResult { + success: boolean + error?: string[] + info: { + blockNumber?: string + blockHash?: string + transactionHash?: string + } +} + /** * 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: @@ -144,105 +147,41 @@ export async function issue({ ) } } - -interface RevokeResult { - success: boolean - error?: string[] - info: { - blockNumber?: string - blockHash?: string - transactionHash?: string - } -} - -interface BlockchainResponse { - blockNumber: string - status: { - finalized: string - } - txHash: string -} - /** * Revokes a Kilt credential on the blockchain, making it invalid. * * @param params Named parameters for the revocation process. - * @param params.issuer Interfaces for interacting with the issuer identity. - * @param params.issuer.didDocument The DID Document of the issuer revoking the credential. - * @param params.issuer.signers Array of signer interfaces for credential authorization. - * @param params.issuer.submitter The submitter can be one of: - * - A MultibaseKeyPair for signing transactions - * - A Ed25519 type keypair for blockchain interactions - * The submitter will be used to cover transaction fees and blockchain operations. - * @param params.credential The Verifiable Credential to be revoked. Must contain a valid credential ID. - * @param issuer - * @param credential - * @returns An object containing: - * - success: Boolean indicating if revocation was successful - * - error?: Array of error messages if revocation failed - * - info: Object containing blockchain transaction details: - * - blockNumber?: The block number where revocation was included - * - blockHash?: The hash of the finalized block - * - transactionHash?: The hash of the revocation transaction. - * @throws Will return error response if: - * - Credential ID is invalid or cannot be decoded - * - DID authorization fails - * - Transaction signing or submission fails. + * @param params.credential The Verifiable Credential to be revoked. + * @param params.issuer Options for the issuer performing the revocation. + * @param params.proofOptions Optional parameters for proof configuration. + * @param params.proofOptions.proofType The type of proof to use for revocation. Currently only supports KiltAttestationProofV1. + * @returns Promise containing the revocation result. + * @throws {SDKError} When an unsupported proof type is provided. */ -export async function revoke( - issuer: IssuerOptions, +export async function revoke({ + credential, + issuer, + proofOptions = {}, +}: { credential: VerifiableCredential -): Promise { - try { - if (!credential.id) { - throw new Error('Credential ID is required for revocation') - } - - const rootHash = credential.id.split(':').pop() - if (!rootHash) { - throw new Error('Invalid credential ID format') - } - - const decodedroothash = base58Decode(rootHash) - const { didDocument, signers, submitter } = issuer - const api = ConfigService.get('api') - - console.log(didDocument) - - const revokeTx = api.tx.attestation.revoke(decodedroothash, null) as any - const [Txsubmitter] = (await Signers.getSignersForKeypair({ - keypair: submitter as MultibaseKeyPair, - type: 'Ed25519', - })) as Array> - const authorizedTx = await authorizeTx( - didDocument, - revokeTx, - signers as SignerInterface[], - Txsubmitter.id - ) - - const response = (await Blockchain.signAndSubmitTx( - authorizedTx, - Txsubmitter - )) as unknown as BlockchainResponse - - const responseObj = JSON.parse(JSON.stringify(response)) - - return { - success: true, - info: { - blockNumber: responseObj.blockNumber, - blockHash: responseObj.status.finalized, - transactionHash: responseObj.txHash, - }, - } - } catch (error: unknown) { - const errorMessage = - error instanceof Error ? error.message : 'Unknown error occurred' - return { - success: false, - error: [errorMessage], - info: {}, + issuer: IssuerOptions + proofOptions?: { + proofType?: string + } +}): Promise { + const { proofType } = proofOptions + switch (proofType) { + case undefined: + case KiltAttestationProofV1.PROOF_TYPE: { + const result = await KiltRevocationStatusV1.revoke( + issuer as IssuerOptions, + credential as VerifiableCredential + ) + return result } + default: + throw new SDKErrors.SDKError( + `Only proof type ${KiltAttestationProofV1.PROOF_TYPE} is currently supported.` + ) } } From c8de0c14c67d8fdc68e7d67574c7c3df608ba05f Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 14 Jan 2025 18:06:01 +0100 Subject: [PATCH 05/13] feat: improve revocation implementation --- .../src/V1/KiltAttestationProofV1.ts | 89 ++--------- .../src/V1/KiltRevocationStatusV1.ts | 145 ++++++++---------- packages/credentials/src/V1/common.ts | 63 +++++++- packages/credentials/src/issuer.ts | 61 ++++---- tests/bundle/bundle-test.ts | 39 ++--- 5 files changed, 194 insertions(+), 203 deletions(-) diff --git a/packages/credentials/src/V1/KiltAttestationProofV1.ts b/packages/credentials/src/V1/KiltAttestationProofV1.ts index f74423ae2..df3b6d292 100644 --- a/packages/credentials/src/V1/KiltAttestationProofV1.ts +++ b/packages/credentials/src/V1/KiltAttestationProofV1.ts @@ -30,30 +30,20 @@ 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, -} from '@kiltprotocol/did' +import { fromChain, getFullDid, validateDid } from '@kiltprotocol/did' import type { Did, ICType, IDelegationNode, - KiltAddress, SharedArguments, - SignerInterface, } from '@kiltprotocol/types' -import { Caip19, JsonSchema, SDKErrors, Signers } from '@kiltprotocol/utils' +import { Caip19, JsonSchema, SDKErrors } 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' @@ -67,6 +57,7 @@ import { assertMatchingConnection, credentialIdFromRootHash, credentialIdToRootHash, + defaultTxSubmit, delegationIdFromAttesterDelegation, ExpandedContents, getDelegationNodeIdForCredential, @@ -210,8 +201,7 @@ export function calculateRootHash( return delegationIdFromAttesterDelegation(entry) } throw new SDKErrors.CredentialMalformedError( - `unknown type ${ - (entry as { type: string }).type + `unknown type ${(entry as { type: string }).type } in federatedTrustModel` ) }), @@ -267,9 +257,9 @@ async function verifyAttestedAt( const cTypeId = CType.hashToId(cTypeHash.toHex()) const delegationId = authorization.isSome ? ( - (authorization.unwrap() as RuntimeCommonAuthorizationAuthorizationId) - .value ?? authorization.unwrap() - ).toHex() + (authorization.unwrap() as RuntimeCommonAuthorizationAuthorizationId) + .value ?? authorization.unwrap() + ).toHex() : null return { verified: true, @@ -472,8 +462,7 @@ export async function verify( } default: { throw new SDKErrors.CredentialMalformedError( - `unknown type ${ - (i as { type: string }).type + `unknown type ${(i as { type: string }).type } in federatedTrustModel` ) } @@ -571,9 +560,9 @@ export type UnissuedCredential = Omit< export function initializeProof( credential: UnissuedCredential ): [ - KiltAttestationProofV1, - Parameters -] { + KiltAttestationProofV1, + Parameters + ] { const { credentialSubject, nonTransferable } = credential if (nonTransferable !== true) { @@ -662,56 +651,6 @@ export function finalizeProof( } } -async function defaultTxSubmit({ - didDocument, - call, - signers, - submitter, -}: SharedArguments & { - call: Extrinsic -}): Promise { - let submitterAddress: KiltAddress - let accountSigners: SignerInterface[] = [] - if (typeof submitter === 'string') { - submitterAddress = submitter - accountSigners = ( - await Promise.all( - signers.map((keypair) => - 'algorithm' in keypair - ? [keypair] - : Signers.getSignersForKeypair({ keypair }) - ) - ) - ).flat() - } else if ('algorithm' in submitter) { - submitterAddress = submitter.id - accountSigners = [submitter] - } else { - accountSigners = await Signers.getSignersForKeypair({ - keypair: submitter, - }) - submitterAddress = accountSigners[0].id as KiltAddress - } - - let extrinsic = await authorizeTx( - didDocument, - call, - await signersForDid(didDocument, ...signers), - submitterAddress - ) - - if (!extrinsic.isSigned) { - extrinsic = await extrinsic.signAsync(submitterAddress, { - signer: Signers.getPolkadotSigner(accountSigners), - }) - } - const result = await Blockchain.submitSignedTx(extrinsic, { - resolveOn: Blockchain.IS_FINALIZED, - }) - const blockHash = result.status.asFinalized - return { block: { hash: blockHash.toHex() } } -} - /** * * Creates a complete {@link KiltAttestationProofV1} for issuing a new credential. @@ -749,9 +688,9 @@ export async function issue( typeof submitter === 'function' ? submitter(args) : defaultTxSubmit({ - ...args, - submitter, - }) + ...args, + submitter, + }) let result = await transactionPromise if ('status' in result) { diff --git a/packages/credentials/src/V1/KiltRevocationStatusV1.ts b/packages/credentials/src/V1/KiltRevocationStatusV1.ts index 237781418..c2972a161 100644 --- a/packages/credentials/src/V1/KiltRevocationStatusV1.ts +++ b/packages/credentials/src/V1/KiltRevocationStatusV1.ts @@ -9,51 +9,26 @@ import { u8aEq, u8aToHex, u8aToU8a } from '@polkadot/util' import { base58Decode, base58Encode } from '@polkadot/util-crypto' import type { ApiPromise } from '@polkadot/api' import type { U8aLike } from '@polkadot/util/types' -import { authorizeTx } from '@kiltprotocol/did' import { ConfigService } from '@kiltprotocol/config' -import type { - Caip2ChainId, - KiltAddress, - SignerInterface, - MultibaseKeyPair, -} from '@kiltprotocol/types' -import { Caip2, SDKErrors, Signers } from '@kiltprotocol/utils' -import { Blockchain } from '@kiltprotocol/chain-helpers' +import type { Caip2ChainId, SharedArguments } from '@kiltprotocol/types' +import { Caip2, SDKErrors } from '@kiltprotocol/utils' +import { Extrinsic } from '@polkadot/types/interfaces/types.js' import * as CType from '../ctype/index.js' import * as Attestation from '../attestation/index.js' import { assertMatchingConnection, + defaultTxSubmit, getDelegationNodeIdForCredential, } from './common.js' import type { IssuerOptions } from '../interfaces.js' -import type { - KiltCredentialV1, - KiltRevocationStatusV1, - VerifiableCredential, -} from './types.js' +import type { KiltCredentialV1 } from './types.js' + +import { type KiltRevocationStatusV1 } from './types.js' export type Interface = KiltRevocationStatusV1 export const STATUS_TYPE = 'KiltRevocationStatusV1' -interface RevokeResult { - success: boolean - error?: string[] - info: { - blockNumber?: string - blockHash?: string - transactionHash?: string - } -} - -interface BlockchainResponse { - blockNumber: string - status: { - finalized: string - } - txHash: string -} - /** * Revokes a Kilt credential on the blockchain, making it invalid. * @@ -68,6 +43,8 @@ interface BlockchainResponse { * @param params.credential The Verifiable Credential to be revoked. Must contain a valid credential ID. * @param issuer * @param credential + * @param opts + * @param opts.api * @returns An object containing: * - success: Boolean indicating if revocation was successful * - error?: Array of error messages if revocation failed @@ -80,59 +57,71 @@ interface BlockchainResponse { * - DID authorization fails * - Transaction signing or submission fails. */ + +/** + * @param credentialStatus The credential status propoerty of the Verifiable credential. + * @param opts Additional parameters. + * @param opts.api An optional polkadot-js/api instance connected to the blockchain network on which the credential is anchored. + * @param params.issuer Interfaces for interacting with the issuer identity. + * @param params.issuer.didDocument The DID Document of the issuer revoking the credential. + * @param params.issuer.signers Array of signer interfaces for credential authorization. + * @param params.issuer.submitter The submitter can be one of: + * - A MultibaseKeyPair for signing transactions + * - A Ed25519 type keypair for blockchain interactions + * The submitter will be used to cover transaction fees and blockchain operations. + */ export async function revoke( + credentialStatus: KiltRevocationStatusV1, issuer: IssuerOptions, - credential: VerifiableCredential -): Promise { - try { - if (!credential.id) { - throw new Error('Credential ID is required for revocation') - } - - const rootHash = credential.id.split(':').pop() - if (!rootHash) { - throw new Error('Invalid credential ID format') - } - - const decodedroothash = base58Decode(rootHash) - const { didDocument, signers, submitter } = issuer - const api = ConfigService.get('api') - - const revokeTx = api.tx.attestation.revoke(decodedroothash, null) as any - const [Txsubmitter] = (await Signers.getSignersForKeypair({ - keypair: submitter as MultibaseKeyPair, - type: 'Ed25519', - })) as Array> - const authorizedTx = await authorizeTx( - didDocument, - revokeTx, - signers as SignerInterface[], - Txsubmitter.id + opts: { api?: ApiPromise } = {} +): Promise { + if (credentialStatus?.type !== STATUS_TYPE) + throw new TypeError( + `The credential must have a credentialStatus of type ${STATUS_TYPE}` + ) + const { api = ConfigService.get('api') } = opts + const { assetNamespace, assetReference, assetInstance } = + assertMatchingConnection(api, { credentialStatus }) + if (assetNamespace !== 'kilt' || assetReference !== 'attestation') { + throw new Error( + `Cannot handle revocation status checks for asset type ${assetNamespace}:${assetReference}` ) + } + if (!assetInstance) { + throw new SDKErrors.CredentialMalformedError( + "The attestation record's CAIP-19 identifier must contain an asset index ('token_id') decoding to the credential root hash" + ) + } + const rootHash = base58Decode(assetInstance) - const response = (await Blockchain.signAndSubmitTx( - authorizedTx, - Txsubmitter - )) as unknown as BlockchainResponse + const { didDocument, signers, submitter } = issuer - const responseObj = JSON.parse(JSON.stringify(response)) + const call = api.tx.attestation.revoke(rootHash, null) - return { - success: true, - info: { - blockNumber: responseObj.blockNumber, - blockHash: responseObj.status.finalized, - transactionHash: responseObj.txHash, - }, - } - } catch (error: unknown) { - const errorMessage = - error instanceof Error ? error.message : 'Unknown error occurred' - return { - success: false, - error: [errorMessage], - info: {}, + const args: Pick & { + call: Extrinsic + } = { + didDocument, + signers, + api, + call, + } + 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 } } diff --git a/packages/credentials/src/V1/common.ts b/packages/credentials/src/V1/common.ts index ade89432a..ae299e5d4 100644 --- a/packages/credentials/src/V1/common.ts +++ b/packages/credentials/src/V1/common.ts @@ -9,10 +9,19 @@ import type { ApiPromise } from '@polkadot/api' import { base58Decode, base58Encode } from '@polkadot/util-crypto' import { hexToU8a } from '@polkadot/util' -import type { HexString } from '@kiltprotocol/types' -import { Caip19, Caip2, SDKErrors } from '@kiltprotocol/utils' +import type { + HexString, + KiltAddress, + SharedArguments, + SignerInterface, +} from '@kiltprotocol/types' +import { Caip19, Caip2, SDKErrors, Signers } from '@kiltprotocol/utils' import type { KiltAttesterDelegationV1, KiltCredentialV1 } from './types.js' +import { Extrinsic } from '@polkadot/types/interfaces/types.js' +import { SimplifiedTransactionResult } from '../interfaces.js' +import { authorizeTx, signersForDid } from '@kiltprotocol/did' +import { Blockchain } from '@kiltprotocol/chain-helpers' export const spiritnetGenesisHash = hexToU8a( '0x411f057b9107718c9624d6aa4a3f23c1653898297f3d4d529d9bb6511a39dd21' @@ -154,3 +163,53 @@ export function credentialIdFromRootHash( const bytes = typeof rootHash === 'string' ? hexToU8a(rootHash) : rootHash return `${KILT_CREDENTIAL_IRI_PREFIX}${base58Encode(bytes, false)}` } + +export async function defaultTxSubmit({ + didDocument, + call, + signers, + submitter, +}: SharedArguments & { + call: Extrinsic +}): Promise { + let submitterAddress: KiltAddress + let accountSigners: SignerInterface[] = [] + if (typeof submitter === 'string') { + submitterAddress = submitter + accountSigners = ( + await Promise.all( + signers.map((keypair) => + 'algorithm' in keypair + ? [keypair] + : Signers.getSignersForKeypair({ keypair }) + ) + ) + ).flat() + } else if ('algorithm' in submitter) { + submitterAddress = submitter.id + accountSigners = [submitter] + } else { + accountSigners = await Signers.getSignersForKeypair({ + keypair: submitter, + }) + submitterAddress = accountSigners[0].id as KiltAddress + } + + let extrinsic = await authorizeTx( + didDocument, + call, + await signersForDid(didDocument, ...signers), + submitterAddress + ) + + if (!extrinsic.isSigned) { + extrinsic = await extrinsic.signAsync(submitterAddress, { + signer: Signers.getPolkadotSigner(accountSigners), + }) + } + const result = await Blockchain.submitSignedTx(extrinsic, { + resolveOn: Blockchain.IS_FINALIZED, + }) + const blockHash = result.status.asFinalized + return { block: { hash: blockHash.toHex() } } +} diff --git a/packages/credentials/src/issuer.ts b/packages/credentials/src/issuer.ts index c18a13999..9521b05dc 100644 --- a/packages/credentials/src/issuer.ts +++ b/packages/credentials/src/issuer.ts @@ -19,16 +19,6 @@ import { export type { IssuerOptions } -interface RevokeResult { - success: boolean - error?: string[] - info: { - blockNumber?: string - blockHash?: string - transactionHash?: string - } -} - /** * 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: @@ -148,7 +138,6 @@ export async function issue({ } } /** - * Revokes a Kilt credential on the blockchain, making it invalid. * * @param params Named parameters for the revocation process. * @param params.credential The Verifiable Credential to be revoked. @@ -158,30 +147,42 @@ export async function issue({ * @returns Promise containing the revocation result. * @throws {SDKError} When an unsupported proof type is provided. */ + +/** + * Revokes a Kilt credential on the blockchain, making it invalid. + * + * @param params Holds all named parameters. + * @param params.credential A credential document. + * @param params.issuer Interfaces for interacting with the issuer identity for the purpose of revoking the credential. + * @param params.issuer.didDocument The DID Document of the issuer. + * @param params.issuer.signers An array of signer interfaces, each allowing to request signatures made with a key associated with the issuer DID Document. + * The function will select the first signer that matches requirements around signature algorithm and relationship of the key to the DID as given by the DID Document. + * @param params.issuer.submitter Some proof types require making transactions to effect state changes on the KILT blockchain. + * The blockchain account whose address is specified here will be used to cover all transaction fees and deposits due for this operation. + * As transactions to the blockchain need to be signed, `signers` is expected to contain a signer interface where the `id` matches this address. + * + * Alternatively, you can pass a {@link SubmitOverride} callback that takes care of Did-authorizing and submitting the transaction. + * If you are using a service that helps you submit and pay for transactions, this is your point of integration to it. + * + * @throws if the credential format is invalid or the revocation fails. + */ export async function revoke({ credential, issuer, - proofOptions = {}, }: { credential: VerifiableCredential issuer: IssuerOptions - proofOptions?: { - proofType?: string - } -}): Promise { - const { proofType } = proofOptions - switch (proofType) { - case undefined: - case KiltAttestationProofV1.PROOF_TYPE: { - const result = await KiltRevocationStatusV1.revoke( - issuer as IssuerOptions, - credential as VerifiableCredential - ) - return result - } - default: - throw new SDKErrors.SDKError( - `Only proof type ${KiltAttestationProofV1.PROOF_TYPE} is currently supported.` - ) +}): Promise { + const status = credential.credentialStatus + if (!status || status.type !== KiltRevocationStatusV1.STATUS_TYPE) { + throw new SDKErrors.SDKError( + `Only credential status type ${KiltRevocationStatusV1.STATUS_TYPE} is currently supported.` + ) } + await KiltRevocationStatusV1.revoke( + status as KiltRevocationStatusV1.Interface, + issuer, + {} + ) + return credential } diff --git a/tests/bundle/bundle-test.ts b/tests/bundle/bundle-test.ts index 302a45344..20e7413a7 100644 --- a/tests/bundle/bundle-test.ts +++ b/tests/bundle/bundle-test.ts @@ -280,6 +280,27 @@ async function runAll() { console.log('presentation verified') + // ┏━━━━━━━━━━━━━━━━━━━━━━━┓ + // ┃ Revoke credential ┃ + // ┗━━━━━━━━━━━━━━━━━━━━━━━┛ + // Before revocation, credential status is valid. + let credentialStatus = await Kilt.Verifier.checkStatus({ credential }) + if (!credentialStatus.verified) { + throw new Error('credential already revoked') + } + // Revoke a previously issued credential on chain. + await Kilt.Issuer.revoke({ + issuer: { didDocument, signers, submitter }, + credential, + }) + console.log('credential revoked') + + // After revocation, credential status is invalid. + credentialStatus = await Kilt.Verifier.checkStatus({ credential }) + if (credentialStatus.verified) { + throw new Error('credential did not get revoked') + } + // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ Remove a Verification Method ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ @@ -370,24 +391,6 @@ async function runAll() { console.log('Did deactivated') - // ┏━━━━━━━━━━━━━━━━━━┓ - // ┃ Revoke credential┃ - // ┗━━━━━━━━━━━━━━━━━━┛ - // - // Revoke a previously issued credential on chain - const revokeCredentialResult = await Kilt.Issuer.revoke( - didDocument.id, - credential - ) - - if (!revokeCredentialResult.success) { - throw new Error( - `revoke credential failed: ${revokeCredentialResult.error?.join(', ')}` - ) - } - - console.log('credential revoked') - // Release the connection to the blockchain. await api.disconnect() From 0756da20087924b64b74dba3dc67aca5c6677b0c Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 14 Jan 2025 18:08:41 +0100 Subject: [PATCH 06/13] fix: lint issues --- .../src/V1/KiltAttestationProofV1.ts | 24 ++++++++++--------- .../src/V1/KiltRevocationStatusV1.ts | 1 + packages/credentials/src/V1/common.ts | 11 +++++++-- packages/credentials/src/issuer.ts | 2 +- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/credentials/src/V1/KiltAttestationProofV1.ts b/packages/credentials/src/V1/KiltAttestationProofV1.ts index df3b6d292..739653a0f 100644 --- a/packages/credentials/src/V1/KiltAttestationProofV1.ts +++ b/packages/credentials/src/V1/KiltAttestationProofV1.ts @@ -201,7 +201,8 @@ export function calculateRootHash( return delegationIdFromAttesterDelegation(entry) } throw new SDKErrors.CredentialMalformedError( - `unknown type ${(entry as { type: string }).type + `unknown type ${ + (entry as { type: string }).type } in federatedTrustModel` ) }), @@ -257,9 +258,9 @@ async function verifyAttestedAt( const cTypeId = CType.hashToId(cTypeHash.toHex()) const delegationId = authorization.isSome ? ( - (authorization.unwrap() as RuntimeCommonAuthorizationAuthorizationId) - .value ?? authorization.unwrap() - ).toHex() + (authorization.unwrap() as RuntimeCommonAuthorizationAuthorizationId) + .value ?? authorization.unwrap() + ).toHex() : null return { verified: true, @@ -462,7 +463,8 @@ export async function verify( } default: { throw new SDKErrors.CredentialMalformedError( - `unknown type ${(i as { type: string }).type + `unknown type ${ + (i as { type: string }).type } in federatedTrustModel` ) } @@ -560,9 +562,9 @@ export type UnissuedCredential = Omit< export function initializeProof( credential: UnissuedCredential ): [ - KiltAttestationProofV1, - Parameters - ] { + KiltAttestationProofV1, + Parameters +] { const { credentialSubject, nonTransferable } = credential if (nonTransferable !== true) { @@ -688,9 +690,9 @@ export async function issue( typeof submitter === 'function' ? submitter(args) : defaultTxSubmit({ - ...args, - submitter, - }) + ...args, + submitter, + }) let result = await transactionPromise if ('status' in result) { diff --git a/packages/credentials/src/V1/KiltRevocationStatusV1.ts b/packages/credentials/src/V1/KiltRevocationStatusV1.ts index c2972a161..e736d897d 100644 --- a/packages/credentials/src/V1/KiltRevocationStatusV1.ts +++ b/packages/credentials/src/V1/KiltRevocationStatusV1.ts @@ -69,6 +69,7 @@ export const STATUS_TYPE = 'KiltRevocationStatusV1' * - A MultibaseKeyPair for signing transactions * - A Ed25519 type keypair for blockchain interactions * The submitter will be used to cover transaction fees and blockchain operations. + * @param issuer */ export async function revoke( credentialStatus: KiltRevocationStatusV1, diff --git a/packages/credentials/src/V1/common.ts b/packages/credentials/src/V1/common.ts index ae299e5d4..ca76b3521 100644 --- a/packages/credentials/src/V1/common.ts +++ b/packages/credentials/src/V1/common.ts @@ -17,11 +17,11 @@ import type { } from '@kiltprotocol/types' import { Caip19, Caip2, SDKErrors, Signers } from '@kiltprotocol/utils' -import type { KiltAttesterDelegationV1, KiltCredentialV1 } from './types.js' import { Extrinsic } from '@polkadot/types/interfaces/types.js' -import { SimplifiedTransactionResult } from '../interfaces.js' import { authorizeTx, signersForDid } from '@kiltprotocol/did' import { Blockchain } from '@kiltprotocol/chain-helpers' +import { SimplifiedTransactionResult } from '../interfaces.js' +import type { KiltAttesterDelegationV1, KiltCredentialV1 } from './types.js' export const spiritnetGenesisHash = hexToU8a( '0x411f057b9107718c9624d6aa4a3f23c1653898297f3d4d529d9bb6511a39dd21' @@ -164,6 +164,13 @@ export function credentialIdFromRootHash( return `${KILT_CREDENTIAL_IRI_PREFIX}${base58Encode(bytes, false)}` } +/** + * @param root0 + * @param root0.didDocument + * @param root0.call + * @param root0.signers + * @param root0.submitter + */ export async function defaultTxSubmit({ didDocument, call, diff --git a/packages/credentials/src/issuer.ts b/packages/credentials/src/issuer.ts index 9521b05dc..26677e28d 100644 --- a/packages/credentials/src/issuer.ts +++ b/packages/credentials/src/issuer.ts @@ -164,7 +164,7 @@ export async function issue({ * Alternatively, you can pass a {@link SubmitOverride} callback that takes care of Did-authorizing and submitting the transaction. * If you are using a service that helps you submit and pay for transactions, this is your point of integration to it. * - * @throws if the credential format is invalid or the revocation fails. + * @throws If the credential format is invalid or the revocation fails. */ export async function revoke({ credential, From d6ad31bf4d39ef7a4772fa7bb1213743f130102e Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Tue, 14 Jan 2025 18:11:58 +0100 Subject: [PATCH 07/13] fix: remove unused docs --- .../src/V1/KiltRevocationStatusV1.ts | 29 ------------------- packages/credentials/src/issuer.ts | 10 ------- 2 files changed, 39 deletions(-) diff --git a/packages/credentials/src/V1/KiltRevocationStatusV1.ts b/packages/credentials/src/V1/KiltRevocationStatusV1.ts index e736d897d..bd47fac4b 100644 --- a/packages/credentials/src/V1/KiltRevocationStatusV1.ts +++ b/packages/credentials/src/V1/KiltRevocationStatusV1.ts @@ -29,35 +29,6 @@ export type Interface = KiltRevocationStatusV1 export const STATUS_TYPE = 'KiltRevocationStatusV1' -/** - * Revokes a Kilt credential on the blockchain, making it invalid. - * - * @param params Named parameters for the revocation process. - * @param params.issuer Interfaces for interacting with the issuer identity. - * @param params.issuer.didDocument The DID Document of the issuer revoking the credential. - * @param params.issuer.signers Array of signer interfaces for credential authorization. - * @param params.issuer.submitter The submitter can be one of: - * - A MultibaseKeyPair for signing transactions - * - A Ed25519 type keypair for blockchain interactions - * The submitter will be used to cover transaction fees and blockchain operations. - * @param params.credential The Verifiable Credential to be revoked. Must contain a valid credential ID. - * @param issuer - * @param credential - * @param opts - * @param opts.api - * @returns An object containing: - * - success: Boolean indicating if revocation was successful - * - error?: Array of error messages if revocation failed - * - info: Object containing blockchain transaction details: - * - blockNumber?: The block number where revocation was included - * - blockHash?: The hash of the finalized block - * - transactionHash?: The hash of the revocation transaction. - * @throws Will return error response if: - * - Credential ID is invalid or cannot be decoded - * - DID authorization fails - * - Transaction signing or submission fails. - */ - /** * @param credentialStatus The credential status propoerty of the Verifiable credential. * @param opts Additional parameters. diff --git a/packages/credentials/src/issuer.ts b/packages/credentials/src/issuer.ts index 26677e28d..c77387301 100644 --- a/packages/credentials/src/issuer.ts +++ b/packages/credentials/src/issuer.ts @@ -137,16 +137,6 @@ export async function issue({ ) } } -/** - * - * @param params Named parameters for the revocation process. - * @param params.credential The Verifiable Credential to be revoked. - * @param params.issuer Options for the issuer performing the revocation. - * @param params.proofOptions Optional parameters for proof configuration. - * @param params.proofOptions.proofType The type of proof to use for revocation. Currently only supports KiltAttestationProofV1. - * @returns Promise containing the revocation result. - * @throws {SDKError} When an unsupported proof type is provided. - */ /** * Revokes a Kilt credential on the blockchain, making it invalid. From f5c53833e62cafbaf24153aaef1c2c4698629020 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab <31316147+abdulmth@users.noreply.github.com> Date: Thu, 16 Jan 2025 10:36:32 +0100 Subject: [PATCH 08/13] Apply suggestions from code review Co-authored-by: Raphael Flechtner <39338561+rflechtner@users.noreply.github.com> --- .../src/V1/KiltRevocationStatusV1.ts | 32 +++++++++++++------ packages/credentials/src/issuer.ts | 2 +- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/credentials/src/V1/KiltRevocationStatusV1.ts b/packages/credentials/src/V1/KiltRevocationStatusV1.ts index bd47fac4b..0d253394b 100644 --- a/packages/credentials/src/V1/KiltRevocationStatusV1.ts +++ b/packages/credentials/src/V1/KiltRevocationStatusV1.ts @@ -21,16 +21,14 @@ import { getDelegationNodeIdForCredential, } from './common.js' import type { IssuerOptions } from '../interfaces.js' -import type { KiltCredentialV1 } from './types.js' - -import { type KiltRevocationStatusV1 } from './types.js' +import type { KiltCredentialV1, KiltRevocationStatusV1 } from './types.js' export type Interface = KiltRevocationStatusV1 export const STATUS_TYPE = 'KiltRevocationStatusV1' /** - * @param credentialStatus The credential status propoerty of the Verifiable credential. + * @param credentialStatus The `credentialStatus` property of the Verifiable Credential. * @param opts Additional parameters. * @param opts.api An optional polkadot-js/api instance connected to the blockchain network on which the credential is anchored. * @param params.issuer Interfaces for interacting with the issuer identity. @@ -88,12 +86,28 @@ export async function revoke( 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` - ) + let error: Error | undefined + switch (result.status) { + case 'confirmed': + return + case 'failed': + error = result.asFailed.error + break + case 'rejected': + error = result.asRejected.error + break + case 'unknown': + error = result.asUnknown.error + break + default: + break } - result = result.asConfirmed + throw ( + error ?? + new SDKErrors.SDKError( + `Revocation failed with transaction status ${result?.status}` + ) + ) } } diff --git a/packages/credentials/src/issuer.ts b/packages/credentials/src/issuer.ts index c77387301..2f73b03d5 100644 --- a/packages/credentials/src/issuer.ts +++ b/packages/credentials/src/issuer.ts @@ -154,7 +154,7 @@ export async function issue({ * Alternatively, you can pass a {@link SubmitOverride} callback that takes care of Did-authorizing and submitting the transaction. * If you are using a service that helps you submit and pay for transactions, this is your point of integration to it. * - * @throws If the credential format is invalid or the revocation fails. + * @throws If the credential format is invalid or the revocation fails. */ export async function revoke({ credential, From 053473dd2371bb15206e0aeed5bae4bebb87fda2 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Thu, 16 Jan 2025 11:27:03 +0100 Subject: [PATCH 09/13] chore: improvements --- .../src/V1/KiltRevocationStatusV1.ts | 58 +++++-------------- packages/credentials/src/V1/common.ts | 38 +++++++++++- packages/credentials/src/issuer.ts | 2 +- 3 files changed, 51 insertions(+), 47 deletions(-) diff --git a/packages/credentials/src/V1/KiltRevocationStatusV1.ts b/packages/credentials/src/V1/KiltRevocationStatusV1.ts index 0d253394b..20cff3e94 100644 --- a/packages/credentials/src/V1/KiltRevocationStatusV1.ts +++ b/packages/credentials/src/V1/KiltRevocationStatusV1.ts @@ -6,7 +6,7 @@ */ import { u8aEq, u8aToHex, u8aToU8a } from '@polkadot/util' -import { base58Decode, base58Encode } from '@polkadot/util-crypto' +import { base58Encode } from '@polkadot/util-crypto' import type { ApiPromise } from '@polkadot/api' import type { U8aLike } from '@polkadot/util/types' import { ConfigService } from '@kiltprotocol/config' @@ -16,9 +16,9 @@ import { Extrinsic } from '@polkadot/types/interfaces/types.js' import * as CType from '../ctype/index.js' import * as Attestation from '../attestation/index.js' import { - assertMatchingConnection, defaultTxSubmit, getDelegationNodeIdForCredential, + getRootHash, } from './common.js' import type { IssuerOptions } from '../interfaces.js' import type { KiltCredentialV1, KiltRevocationStatusV1 } from './types.js' @@ -28,42 +28,26 @@ export type Interface = KiltRevocationStatusV1 export const STATUS_TYPE = 'KiltRevocationStatusV1' /** + * Revokes a Verifiable Credential containing a 'KiltRevocationStatusV1. + * * @param credentialStatus The `credentialStatus` property of the Verifiable Credential. * @param opts Additional parameters. * @param opts.api An optional polkadot-js/api instance connected to the blockchain network on which the credential is anchored. - * @param params.issuer Interfaces for interacting with the issuer identity. - * @param params.issuer.didDocument The DID Document of the issuer revoking the credential. - * @param params.issuer.signers Array of signer interfaces for credential authorization. - * @param params.issuer.submitter The submitter can be one of: - * - A MultibaseKeyPair for signing transactions - * - A Ed25519 type keypair for blockchain interactions - * The submitter will be used to cover transaction fees and blockchain operations. * @param issuer + * @param issuer.didDocument The DID Document of the issuer revoking the credential. + * @param issuer.signers Array of signer interfaces for credential authorization. + * @param issuer.submitter The submitter can be one of: + * - A MultibaseKeyPair for signing transactions. + * - A `KeyringPair` for blockchain interactions. + * The submitter will be used to cover transaction fees and blockchain operations. */ export async function revoke( credentialStatus: KiltRevocationStatusV1, issuer: IssuerOptions, opts: { api?: ApiPromise } = {} ): Promise { - if (credentialStatus?.type !== STATUS_TYPE) - throw new TypeError( - `The credential must have a credentialStatus of type ${STATUS_TYPE}` - ) + const rootHash = getRootHash(credentialStatus, opts) const { api = ConfigService.get('api') } = opts - const { assetNamespace, assetReference, assetInstance } = - assertMatchingConnection(api, { credentialStatus }) - if (assetNamespace !== 'kilt' || assetReference !== 'attestation') { - throw new Error( - `Cannot handle revocation status checks for asset type ${assetNamespace}:${assetReference}` - ) - } - if (!assetInstance) { - throw new SDKErrors.CredentialMalformedError( - "The attestation record's CAIP-19 identifier must contain an asset index ('token_id') decoding to the credential root hash" - ) - } - const rootHash = base58Decode(assetInstance) - const { didDocument, signers, submitter } = issuer const call = api.tx.attestation.revoke(rootHash, null) @@ -84,7 +68,7 @@ export async function revoke( submitter, }) - let result = await transactionPromise + const result = await transactionPromise if ('status' in result) { let error: Error | undefined switch (result.status) { @@ -124,24 +108,8 @@ export async function check( opts: { api?: ApiPromise } = {} ): Promise { const { credentialStatus } = credential - if (credentialStatus?.type !== STATUS_TYPE) - throw new TypeError( - `The credential must have a credentialStatus of type ${STATUS_TYPE}` - ) + const rootHash = getRootHash(credentialStatus, opts) const { api = ConfigService.get('api') } = opts - const { assetNamespace, assetReference, assetInstance } = - assertMatchingConnection(api, credential) - if (assetNamespace !== 'kilt' || assetReference !== 'attestation') { - throw new Error( - `Cannot handle revocation status checks for asset type ${assetNamespace}:${assetReference}` - ) - } - if (!assetInstance) { - throw new SDKErrors.CredentialMalformedError( - "The attestation record's CAIP-19 identifier must contain an asset index ('token_id') decoding to the credential root hash" - ) - } - const rootHash = base58Decode(assetInstance) const encoded = await api.query.attestation.attestations(rootHash) if (encoded.isNone) throw new SDKErrors.CredentialUnverifiableError( diff --git a/packages/credentials/src/V1/common.ts b/packages/credentials/src/V1/common.ts index ca76b3521..8a06fb341 100644 --- a/packages/credentials/src/V1/common.ts +++ b/packages/credentials/src/V1/common.ts @@ -8,6 +8,7 @@ import type { ApiPromise } from '@polkadot/api' import { base58Decode, base58Encode } from '@polkadot/util-crypto' import { hexToU8a } from '@polkadot/util' +import { ConfigService } from '@kiltprotocol/config' import type { HexString, @@ -21,7 +22,12 @@ import { Extrinsic } from '@polkadot/types/interfaces/types.js' import { authorizeTx, signersForDid } from '@kiltprotocol/did' import { Blockchain } from '@kiltprotocol/chain-helpers' import { SimplifiedTransactionResult } from '../interfaces.js' -import type { KiltAttesterDelegationV1, KiltCredentialV1 } from './types.js' +import type { + KiltAttesterDelegationV1, + KiltCredentialV1, + KiltRevocationStatusV1, +} from './types.js' +import { STATUS_TYPE } from './KiltRevocationStatusV1.js' export const spiritnetGenesisHash = hexToU8a( '0x411f057b9107718c9624d6aa4a3f23c1653898297f3d4d529d9bb6511a39dd21' @@ -220,3 +226,33 @@ export async function defaultTxSubmit({ const blockHash = result.status.asFinalized return { block: { hash: blockHash.toHex() } } } + +/** + * @param credentialStatus + * @param issuer + * @param opts + * @param opts.api + */ +export function getRootHash( + credentialStatus: KiltRevocationStatusV1, + opts: { api?: ApiPromise } = {} +) { + if (credentialStatus?.type !== STATUS_TYPE) + throw new TypeError( + `The credential must have a credentialStatus of type ${STATUS_TYPE}` + ) + const { api = ConfigService.get('api') } = opts + const { assetNamespace, assetReference, assetInstance } = + assertMatchingConnection(api, { credentialStatus }) + if (assetNamespace !== 'kilt' || assetReference !== 'attestation') { + throw new Error( + `Cannot handle revocation status checks for asset type ${assetNamespace}:${assetReference}` + ) + } + if (!assetInstance) { + throw new SDKErrors.CredentialMalformedError( + "The attestation record's CAIP-19 identifier must contain an asset index ('token_id') decoding to the credential root hash" + ) + } + return base58Decode(assetInstance) +} diff --git a/packages/credentials/src/issuer.ts b/packages/credentials/src/issuer.ts index 2f73b03d5..81286a749 100644 --- a/packages/credentials/src/issuer.ts +++ b/packages/credentials/src/issuer.ts @@ -154,7 +154,7 @@ export async function issue({ * Alternatively, you can pass a {@link SubmitOverride} callback that takes care of Did-authorizing and submitting the transaction. * If you are using a service that helps you submit and pay for transactions, this is your point of integration to it. * - * @throws If the credential format is invalid or the revocation fails. + * @throws If the credential format is invalid or the revocation fails. */ export async function revoke({ credential, From f1a54b26e2d9d9a452962f70c01ee00cf3d6e434 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Thu, 16 Jan 2025 15:25:20 +0100 Subject: [PATCH 10/13] fix: code review --- .../credentials/src/V1/KiltRevocationStatusV1.ts | 16 +++++++++------- packages/credentials/src/V1/common.ts | 6 +++--- packages/credentials/src/issuer.ts | 1 - test-results/.last-run.json | 4 ++++ 4 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 test-results/.last-run.json diff --git a/packages/credentials/src/V1/KiltRevocationStatusV1.ts b/packages/credentials/src/V1/KiltRevocationStatusV1.ts index 20cff3e94..0219689e5 100644 --- a/packages/credentials/src/V1/KiltRevocationStatusV1.ts +++ b/packages/credentials/src/V1/KiltRevocationStatusV1.ts @@ -12,13 +12,13 @@ import type { U8aLike } from '@polkadot/util/types' import { ConfigService } from '@kiltprotocol/config' import type { Caip2ChainId, SharedArguments } from '@kiltprotocol/types' import { Caip2, SDKErrors } from '@kiltprotocol/utils' -import { Extrinsic } from '@polkadot/types/interfaces/types.js' +import { Extrinsic } from '@polkadot/types/interfaces/' import * as CType from '../ctype/index.js' import * as Attestation from '../attestation/index.js' import { defaultTxSubmit, getDelegationNodeIdForCredential, - getRootHash, + getRootHashFromStatusId, } from './common.js' import type { IssuerOptions } from '../interfaces.js' import type { KiltCredentialV1, KiltRevocationStatusV1 } from './types.js' @@ -28,11 +28,9 @@ export type Interface = KiltRevocationStatusV1 export const STATUS_TYPE = 'KiltRevocationStatusV1' /** - * Revokes a Verifiable Credential containing a 'KiltRevocationStatusV1. + * Revokes a Verifiable Credential containing a KiltRevocationStatusV1. * * @param credentialStatus The `credentialStatus` property of the Verifiable Credential. - * @param opts Additional parameters. - * @param opts.api An optional polkadot-js/api instance connected to the blockchain network on which the credential is anchored. * @param issuer * @param issuer.didDocument The DID Document of the issuer revoking the credential. * @param issuer.signers Array of signer interfaces for credential authorization. @@ -40,16 +38,20 @@ export const STATUS_TYPE = 'KiltRevocationStatusV1' * - A MultibaseKeyPair for signing transactions. * - A `KeyringPair` for blockchain interactions. * The submitter will be used to cover transaction fees and blockchain operations. + * @param opts Additional parameters. + * @param opts.api An optional polkadot-js/api instance connected to the blockchain network on which the credential is anchored. */ export async function revoke( credentialStatus: KiltRevocationStatusV1, issuer: IssuerOptions, opts: { api?: ApiPromise } = {} ): Promise { - const rootHash = getRootHash(credentialStatus, opts) + const rootHash = getRootHashFromStatusId(credentialStatus, opts) const { api = ConfigService.get('api') } = opts const { didDocument, signers, submitter } = issuer + // TODO: Support revocations through delegation. + // In this case, the second parameter in this function would needs to be populated. const call = api.tx.attestation.revoke(rootHash, null) const args: Pick & { @@ -108,7 +110,7 @@ export async function check( opts: { api?: ApiPromise } = {} ): Promise { const { credentialStatus } = credential - const rootHash = getRootHash(credentialStatus, opts) + const rootHash = getRootHashFromStatusId(credentialStatus, opts) const { api = ConfigService.get('api') } = opts const encoded = await api.query.attestation.attestations(rootHash) if (encoded.isNone) diff --git a/packages/credentials/src/V1/common.ts b/packages/credentials/src/V1/common.ts index 8a06fb341..5d6c51a7c 100644 --- a/packages/credentials/src/V1/common.ts +++ b/packages/credentials/src/V1/common.ts @@ -18,10 +18,10 @@ import type { } from '@kiltprotocol/types' import { Caip19, Caip2, SDKErrors, Signers } from '@kiltprotocol/utils' -import { Extrinsic } from '@polkadot/types/interfaces/types.js' import { authorizeTx, signersForDid } from '@kiltprotocol/did' import { Blockchain } from '@kiltprotocol/chain-helpers' -import { SimplifiedTransactionResult } from '../interfaces.js' +import { Extrinsic } from '@polkadot/types/interfaces' +import type { SimplifiedTransactionResult } from '../interfaces.js' import type { KiltAttesterDelegationV1, KiltCredentialV1, @@ -233,7 +233,7 @@ export async function defaultTxSubmit({ * @param opts * @param opts.api */ -export function getRootHash( +export function getRootHashFromStatusId( credentialStatus: KiltRevocationStatusV1, opts: { api?: ApiPromise } = {} ) { diff --git a/packages/credentials/src/issuer.ts b/packages/credentials/src/issuer.ts index 81286a749..1d4907473 100644 --- a/packages/credentials/src/issuer.ts +++ b/packages/credentials/src/issuer.ts @@ -6,7 +6,6 @@ */ import { SDKErrors } from '@kiltprotocol/utils' -// eslint-disable-next-line @typescript-eslint/no-unused-vars import type { Did, ICType, IClaimContents } from '@kiltprotocol/types' import type { IssuerOptions } from './interfaces.js' import type { CTypeLoader } from './ctype/index.js' diff --git a/test-results/.last-run.json b/test-results/.last-run.json new file mode 100644 index 000000000..cbcc1fbac --- /dev/null +++ b/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "passed", + "failedTests": [] +} \ No newline at end of file From 66d1920e62283ca67458ff8ac12870a7e7af4cea Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Thu, 16 Jan 2025 15:28:36 +0100 Subject: [PATCH 11/13] chore: remove unnecessary docs --- packages/credentials/src/V1/common.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/credentials/src/V1/common.ts b/packages/credentials/src/V1/common.ts index 5d6c51a7c..db91a042d 100644 --- a/packages/credentials/src/V1/common.ts +++ b/packages/credentials/src/V1/common.ts @@ -170,13 +170,6 @@ export function credentialIdFromRootHash( return `${KILT_CREDENTIAL_IRI_PREFIX}${base58Encode(bytes, false)}` } -/** - * @param root0 - * @param root0.didDocument - * @param root0.call - * @param root0.signers - * @param root0.submitter - */ export async function defaultTxSubmit({ didDocument, call, @@ -227,12 +220,6 @@ export async function defaultTxSubmit({ return { block: { hash: blockHash.toHex() } } } -/** - * @param credentialStatus - * @param issuer - * @param opts - * @param opts.api - */ export function getRootHashFromStatusId( credentialStatus: KiltRevocationStatusV1, opts: { api?: ApiPromise } = {} From bc6ad6351fae542b918141b3dc015fd1eefbaad4 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Thu, 16 Jan 2025 16:45:07 +0100 Subject: [PATCH 12/13] fix: docs --- packages/credentials/src/V1/common.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/credentials/src/V1/common.ts b/packages/credentials/src/V1/common.ts index db91a042d..cfe30ec36 100644 --- a/packages/credentials/src/V1/common.ts +++ b/packages/credentials/src/V1/common.ts @@ -170,6 +170,14 @@ export function credentialIdFromRootHash( return `${KILT_CREDENTIAL_IRI_PREFIX}${base58Encode(bytes, false)}` } +/** + * @param root0 + * @param root0.didDocument DID Document of the authorizing DID. + * @param root0.call Extrinsic to be submitted. + * @param root0.signers An array of signer interfaces, each allowing to request signatures made with a key associated with the issuer DID Document. + * @param root0.submitter Submitter to cover the transaction. + * @private + */ export async function defaultTxSubmit({ didDocument, call, @@ -220,6 +228,11 @@ export async function defaultTxSubmit({ return { block: { hash: blockHash.toHex() } } } +/** + * @param credentialStatus Credential revocation status. + * @param opts + * @param opts.api Overrides the userd Kilt API. + */ export function getRootHashFromStatusId( credentialStatus: KiltRevocationStatusV1, opts: { api?: ApiPromise } = {} From 4b89abe3ff0ce0cef4be488518ee8d20ca54fca7 Mon Sep 17 00:00:00 2001 From: Abdulrahim Al Methiab Date: Thu, 16 Jan 2025 16:50:23 +0100 Subject: [PATCH 13/13] chore: remove test result file --- test-results/.last-run.json | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 test-results/.last-run.json diff --git a/test-results/.last-run.json b/test-results/.last-run.json deleted file mode 100644 index cbcc1fbac..000000000 --- a/test-results/.last-run.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "status": "passed", - "failedTests": [] -} \ No newline at end of file