Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: revoke in issuer #918

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 115 additions & 5 deletions packages/credentials/src/V1/KiltRevocationStatusV1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<RevokeResult> {
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<SignerInterface<'Ed25519', KiltAddress>>
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.
*
Expand Down
63 changes: 57 additions & 6 deletions packages/credentials/src/issuer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,30 @@
* 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'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
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,
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:
Expand Down Expand Up @@ -134,3 +147,41 @@ 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.
* @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<RevokeResult> containing the revocation result.
* @throws {SDKError} When an unsupported proof type is provided.
*/
export async function revoke({
credential,
issuer,
proofOptions = {},
}: {
credential: VerifiableCredential
issuer: IssuerOptions
proofOptions?: {
proofType?: string
}
}): Promise<RevokeResult> {
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.`
)
}
}
18 changes: 18 additions & 0 deletions tests/bundle/bundle-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
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()

Expand Down
Loading