From 10c8696260b565d09e56c090744233036448bd40 Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Wed, 9 Apr 2025 03:21:23 +0000 Subject: [PATCH 01/14] chore: add missing test script in package-core Signed-off-by: Krishna Waske --- packages/core/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index e9f039d6ca..2fe86c0a0a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -20,7 +20,8 @@ "build": "pnpm run clean && pnpm run compile", "clean": "rimraf ./build", "compile": "tsc -p tsconfig.build.json", - "prepublishOnly": "pnpm run build" + "prepublishOnly": "pnpm run build", + "test": "jest" }, "dependencies": { "@animo-id/mdoc": "^0.5.2", From 926909fe425296f5773276bae9002284207a6781 Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Mon, 7 Apr 2025 23:43:48 +0530 Subject: [PATCH 02/14] feat: support w3c revocation Signed-off-by: Krishna Waske feat: support w3c revocation Signed-off-by: Krishna Waske chore: verify credential status Signed-off-by: Krishna Waske chore: rearrange files Signed-off-by: Krishna Waske chore: remove unnecessary code from credentials API Signed-off-by: Krishna Waske chore: remove unnecessary code from credentials API Signed-off-by: Krishna Waske chore: remove bitstring specific credential status from jsonld cred formats Signed-off-by: Krishna Waske chore: add appropriate format based error Signed-off-by: Krishna Waske chore: rename symbol Signed-off-by: Krishna Waske chore: update folder name Signed-off-by: Krishna Waske fix: typing and other minor issues while verifying Bitstring status list credential Signed-off-by: Krishna Waske chore: add named imports from pako Signed-off-by: Krishna Waske chore: update error for verifying bit string status list credential Signed-off-by: Krishna Waske chore: move validate status logic to libraries Signed-off-by: Krishna Waske refactor: Invalidate array of bitStringStatusListCredential Signed-off-by: Krishna Waske refactor: export files from index Signed-off-by: Krishna Waske refactor: separate bitstring statuslist and bitstring status list credential Signed-off-by: Krishna Waske feat: add CredentialStatusBasedOnType Signed-off-by: Krishna Waske reactor: remove duplicate code Signed-off-by: Krishna Waske chore: add comment Signed-off-by: Krishna Waske fix: add credential status compare while verifyReceivedCredentialMatchesRequest Signed-off-by: Krishna Waske fix: remove credentialStatus from unsupported fields while accepting request Signed-off-by: Krishna Waske fix: imports Signed-off-by: Krishna Waske chore: update imports Signed-off-by: Krishna Waske fix: imports Signed-off-by: Krishna Waske chore: update minor type changes Signed-off-by: Krishna Waske fix: remove unnecessary tranformation Signed-off-by: Krishna Waske fix: verification of bitstring status list credential after fetching Signed-off-by: Krishna Waske chore: push pnpm-lock file Signed-off-by: Krishna Waske fix: take claimformat from options instead of from credential record, which might not always be present Signed-off-by: Krishna Waske fix: completed TODO Signed-off-by: Krishna Waske fix: check signature of fetched credential, w3cjwt support Signed-off-by: Krishna Waske --- packages/core/package.json | 3 + .../src/modules/vc/W3cCredentialService.ts | 31 +++ .../modules/vc/W3cCredentialServiceOptions.ts | 21 ++ .../core/src/modules/vc/W3cCredentialsApi.ts | 10 + .../W3cJsonLdCredentialService.ts | 33 ++- .../libraries/credentialStatus.ts | 49 ++++ .../vc/jwt-vc/W3cJwtCredentialService.ts | 20 +- .../vc/models/credential/W3cCredential.ts | 4 +- .../models/credential/W3cCredentialStatus.ts | 23 -- .../vc/models/credential/W3cJsonCredential.ts | 2 + .../W3cCredentialStatus.ts | 35 +++ .../BitStringStatusList.ts | 131 ++++++++++ .../BitStringStatusListCredential.ts | 232 ++++++++++++++++++ .../VerifyBitStringCredentialStatus.ts | 127 ++++++++++ .../bitstring-status-list/index.ts | 3 + .../credential/w3c-credential-status/index.ts | 1 + .../src/modules/credentials/CredentialsApi.ts | 2 +- .../formats/jsonld/JsonLdCredentialFormat.ts | 1 + .../jsonld/JsonLdCredentialFormatService.ts | 9 +- pnpm-lock.yaml | 13 +- 20 files changed, 713 insertions(+), 37 deletions(-) create mode 100644 packages/core/src/modules/vc/data-integrity/libraries/credentialStatus.ts delete mode 100644 packages/core/src/modules/vc/models/credential/W3cCredentialStatus.ts create mode 100644 packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts create mode 100644 packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts create mode 100644 packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusListCredential.ts create mode 100644 packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts create mode 100644 packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/index.ts create mode 100644 packages/core/src/modules/vc/models/credential/w3c-credential-status/index.ts diff --git a/packages/core/package.json b/packages/core/package.json index e46da5057e..505cf01968 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,6 +58,8 @@ "lru_map": "^0.4.1", "make-error": "^1.3.6", "object-inspect": "^1.10.3", + "pako": "^2.1.0", + "query-string": "^7.0.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", "tsyringe": "^4.8.0", @@ -69,6 +71,7 @@ "devDependencies": { "@types/events": "^3.0.0", "@types/object-inspect": "^1.8.0", + "@types/pako": "^2.0.3", "@types/uuid": "^9.0.1", "@types/varint": "^6.0.0", "nock": "^14.0.0-beta.19", diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index 175a4ca3c6..ab0c732918 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -22,6 +22,7 @@ import type { import { CredoError } from '../../error' import { injectable } from '../../plugins' +import { RevokeCredentialOptions } from './W3cCredentialServiceOptions' import { CREDENTIALS_CONTEXT_V1_URL } from './constants' import { W3cJsonLdVerifiableCredential } from './data-integrity' import { W3cJsonLdCredentialService } from './data-integrity/W3cJsonLdCredentialService' @@ -183,6 +184,36 @@ export class W3cCredentialService { return w3cCredentialRecord } + /** + * Revoke a credential by issuer + * associated with the credential record. + * + * @param credentialRecordId The id of the credential record for which to revoke the credential + * @returns Revoke credential notification message + * + */ + public async revokeCredential( + agentContext: AgentContext, + options: RevokeCredentialOptions + ) { + // Considering a revoker of a credential can be anyone apart from the issuer + // Not sure, if we need to get Credential record, as it might not be present for ayone apart from the issuer + // const credentialRecod = await this.getCredentialRecordById(agentContext, options.credentialRecordId) + // if (!credentialRecod) { + // throw new CredoError(`Credential with id ${options.credentialRecordId} not found`) + // } + + if (options.format === ClaimFormat.JwtVc) { + const revoked = await this.w3cJwtCredentialService.revokeCredential(agentContext, options) + return revoked as unknown as W3cVerifiableCredential + } else if (options.format === ClaimFormat.LdpVc) { + const revoked = await this.w3cJsonLdCredentialService.revokeCredential(agentContext, options) + return revoked as unknown as W3cVerifiableCredential + } else { + throw new CredoError(`Unsupported format in options. Format must be either 'jwt_vc' or 'ldp_vc'`) + } + } + public async removeCredentialRecord(agentContext: AgentContext, id: string) { await this.w3cCredentialRepository.deleteById(agentContext, id) } diff --git a/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts b/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts index 806a9f9d11..de66fd563b 100644 --- a/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts +++ b/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts @@ -27,6 +27,12 @@ export type W3cSignPresentationOptions = + Format extends ClaimFormat.JwtVc + ? W3cJwtRevokeCredentialOptions + : Format extends ClaimFormat.LdpVc + ? W3cJsonLdRevokeCredentialOptions + : W3cJwtRevokeCredentialOptions | W3cJsonLdRevokeCredentialOptions interface W3cSignCredentialOptionsBase { /** @@ -191,3 +197,18 @@ export interface W3cJsonLdVerifyPresentationOptions extends W3cVerifyPresentatio export interface StoreCredentialOptions { credential: W3cVerifiableCredential } + +/** + * Interface for W3cCredentialsApi.revokeCredential. revoke a w3c credential. + */ +export interface RevokeCredentialOptionsBase { + credentialRecordId: string +} + +export interface W3cJwtRevokeCredentialOptions extends RevokeCredentialOptionsBase { + format: ClaimFormat.JwtVc +} + +export interface W3cJsonLdRevokeCredentialOptions extends RevokeCredentialOptionsBase { + format: ClaimFormat.LdpVc +} diff --git a/packages/core/src/modules/vc/W3cCredentialsApi.ts b/packages/core/src/modules/vc/W3cCredentialsApi.ts index 71590b871f..d24cfef182 100644 --- a/packages/core/src/modules/vc/W3cCredentialsApi.ts +++ b/packages/core/src/modules/vc/W3cCredentialsApi.ts @@ -1,5 +1,6 @@ import type { Query, QueryOptions } from '../../storage/StorageService' import type { + RevokeCredentialOptions, StoreCredentialOptions, W3cCreatePresentationOptions, W3cSignCredentialOptions, @@ -14,6 +15,8 @@ import { AgentContext } from '../../agent' import { injectable } from '../../plugins' import { W3cCredentialService } from './W3cCredentialService' +import { W3cJsonLdVerifiableCredential } from './data-integrity' +import { W3cJwtVerifiableCredential } from './jwt-vc' /** * @public @@ -44,6 +47,13 @@ export class W3cCredentialsApi { return this.w3cCredentialService.getCredentialRecordById(this.agentContext, id) } + // Revoke Credential Methods + public async revokeCredential( + options: RevokeCredentialOptions + ): Promise { + return this.w3cCredentialService.revokeCredential(this.agentContext, options) + } + public async findCredentialRecordsByQuery( query: Query, queryOptions?: QueryOptions diff --git a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts index 6824ecf8de..e3b74c698a 100644 --- a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts +++ b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts @@ -2,12 +2,13 @@ import type { AgentContext } from '../../../agent/context' import type { Key } from '../../../crypto/Key' import type { SingleOrArray } from '../../../utils' import type { + W3cJsonLdRevokeCredentialOptions, W3cJsonLdSignCredentialOptions, W3cJsonLdSignPresentationOptions, W3cJsonLdVerifyCredentialOptions, W3cJsonLdVerifyPresentationOptions, } from '../W3cCredentialServiceOptions' -import type { W3cVerifyCredentialResult, W3cVerifyPresentationResult } from '../models' +import { ClaimFormat, type W3cVerifyCredentialResult, type W3cVerifyPresentationResult } from '../models' import type { W3cJsonCredential } from '../models/credential/W3cJsonCredential' import type { W3cJsonLdDeriveProofOptions } from './deriveProof' @@ -23,6 +24,7 @@ import { w3cDate } from '../util' import { SignatureSuiteRegistry } from './SignatureSuiteRegistry' import { deriveProof } from './deriveProof' import { assertOnlyW3cJsonLdVerifiableCredentials } from './jsonldUtil' +import { validateStatus } from './libraries/credentialStatus' import jsonld from './libraries/jsonld' import vc from './libraries/vc' import { W3cJsonLdVerifiableCredential } from './models' @@ -109,10 +111,12 @@ export class W3cJsonLdCredentialService { credential: JsonTransformer.toJSON(options.credential), suite: suites, documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), - checkStatus: ({ credential }: { credential: W3cJsonCredential }) => { - // Only throw error if credentialStatus is present - if (verifyCredentialStatus && 'credentialStatus' in credential) { - throw new CredoError('Verifying credential status for JSON-LD credentials is currently not supported') + checkStatus: async ({ credential }: { credential: W3cJsonCredential }) => { + if (verifyCredentialStatus && credential.credentialStatus) { + // await verifyBitStringCredentialStatus(credential, agentContext) + // Add a verification function that then checks which type of credentailStatus we have + // If the type is supported, we validate it and return the result + await validateStatus(credential.credentialStatus, agentContext, ClaimFormat.LdpVc) } return { verified: true, @@ -259,12 +263,24 @@ export class W3cJsonLdCredentialService { ) const allSuites = presentationSuites.concat(...credentialSuites) + const verifyCredentialStatus = options.verifyCredentialStatus ?? true const verifyOptions: Record = { presentation: JsonTransformer.toJSON(options.presentation), suite: allSuites, challenge: options.challenge, domain: options.domain, documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), + checkStatus: async ({ credential }: { credential: W3cJsonCredential }) => { + if (verifyCredentialStatus && credential.credentialStatus) { + // await verifyBitStringCredentialStatus(credential, agentContext) + // Add a verification function that then checks which type of credentailStatus we have + // If the type is supported, we validate it and return the result + await validateStatus(credential.credentialStatus, agentContext, ClaimFormat.LdpVc) + } + return { + verified: true, + } + }, } // this is a hack because vcjs throws if purpose is passed as undefined or null @@ -316,6 +332,13 @@ export class W3cJsonLdCredentialService { return proof } + // temporarily disable no unused var + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async revokeCredential(_agentContext: AgentContext, _options: W3cJsonLdRevokeCredentialOptions) { + // revoke jwt cred + throw new CredoError(`Revocation support not implemented for JsonLd`) + } + public getVerificationMethodTypesByProofType(proofType: string): string[] { return this.signatureSuiteRegistry.getByProofType(proofType).verificationMethodTypes } diff --git a/packages/core/src/modules/vc/data-integrity/libraries/credentialStatus.ts b/packages/core/src/modules/vc/data-integrity/libraries/credentialStatus.ts new file mode 100644 index 0000000000..3c988342cc --- /dev/null +++ b/packages/core/src/modules/vc/data-integrity/libraries/credentialStatus.ts @@ -0,0 +1,49 @@ +import type { AgentContext } from '../../../../agent' +import type { W3cCredentialStatus } from '../../models/credential/w3c-credential-status/W3cCredentialStatus' + + +import { CredoError } from '../../../../error/CredoError' +import { + BitStringStatusListEntry, + verifyBitStringCredentialStatus, +} from '../../models/credential/w3c-credential-status' +import { W3cCredentialStatusSupportedTypes } from '../../models/credential/w3c-credential-status/W3cCredentialStatus' +import { SingleOrArray } from '../../../../utils' +import { ClaimFormat } from '../../models' + +// Function to validate the status using the updated method +export const validateStatus = async ( + credentialStatus: SingleOrArray, + agentContext: AgentContext, + credentialFormat: ClaimFormat.JwtVc | ClaimFormat.LdpVc +): Promise => { + + if (Array.isArray(credentialStatus)) { + agentContext.config.logger.debug('Credential status type is array') + throw new CredoError( + 'Invalid credential status type. Currently only a single credentialStatus is supported per credential' + ) + } + + switch (credentialStatus.type) { + case W3cCredentialStatusSupportedTypes.BitstringStatusListEntry: + agentContext.config.logger.debug('Credential status type is BitstringStatusListEntry') + try { + await verifyBitStringCredentialStatus( + credentialStatus as unknown as BitStringStatusListEntry, + agentContext, + credentialFormat + ) + } catch (errors) { + throw new CredoError(`Error while validating credential status`, errors) + } + break + default: + throw new CredoError( + `Invalid credential status type. Supported types are: ${Object.values(W3cCredentialStatusSupportedTypes).join( + ', ' + )}` + ) + } + return true +} diff --git a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts index eef0ee143f..34c9c18f63 100644 --- a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts +++ b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts @@ -2,12 +2,13 @@ import type { AgentContext } from '../../../agent/context' import type { VerifyJwsResult } from '../../../crypto/JwsService' import type { DidPurpose, VerificationMethod } from '../../dids' import type { + W3cJwtRevokeCredentialOptions, W3cJwtSignCredentialOptions, W3cJwtSignPresentationOptions, W3cJwtVerifyCredentialOptions, W3cJwtVerifyPresentationOptions, } from '../W3cCredentialServiceOptions' -import type { SingleValidationResult, W3cVerifyCredentialResult, W3cVerifyPresentationResult } from '../models' +import { ClaimFormat, type SingleValidationResult, type W3cVerifyCredentialResult, type W3cVerifyPresentationResult } from '../models' import { JwsService } from '../../../crypto' import { getJwkClassFromJwaSignatureAlgorithm, getJwkFromKey } from '../../../crypto/jose/jwk' @@ -21,6 +22,7 @@ import { W3cJwtVerifiableCredential } from './W3cJwtVerifiableCredential' import { W3cJwtVerifiablePresentation } from './W3cJwtVerifiablePresentation' import { getJwtPayloadFromCredential } from './credentialTransformer' import { getJwtPayloadFromPresentation } from './presentationTransformer' +import { validateStatus } from '../data-integrity/libraries/credentialStatus' /** * Supports signing and verification of credentials according to the [Verifiable Credential Data Model](https://www.w3.org/TR/vc-data-model) @@ -195,9 +197,13 @@ export class W3cJwtCredentialService { isValid: true, } } else if (verifyCredentialStatus && credential.credentialStatus) { + // TODO: Add similar verification for JWT VCs + // validationResults.validations.credentialStatus = { + // isValid: false, + // error: new CredoError('Verifying credential status is not supported for JWT VCs'), + // } validationResults.validations.credentialStatus = { - isValid: false, - error: new CredoError('Verifying credential status is not supported for JWT VCs'), + isValid: await validateStatus(credential.credentialStatus, agentContext, ClaimFormat.JwtVc) } } @@ -376,6 +382,7 @@ export class W3cJwtCredentialService { } } + // Already verifying credentialStatus, so might not need to have to check credential status explicitly here const credentialResult = await this.verifyCredential(agentContext, { credential, verifyCredentialStatus: options.verifyCredentialStatus, @@ -540,4 +547,11 @@ export class W3cJwtCredentialService { return verificationMethod } + + // temporarily disable no unused var + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async revokeCredential(_agentContext: AgentContext, _options: W3cJwtRevokeCredentialOptions) { + // revoke jwt cred + throw new CredoError(`Revocation support not implemented for jwtVc`) + } } diff --git a/packages/core/src/modules/vc/models/credential/W3cCredential.ts b/packages/core/src/modules/vc/models/credential/W3cCredential.ts index b719d66420..938fa3eb29 100644 --- a/packages/core/src/modules/vc/models/credential/W3cCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cCredential.ts @@ -13,9 +13,9 @@ import { CREDENTIALS_CONTEXT_V1_URL, VERIFIABLE_CREDENTIAL_TYPE } from '../../co import { IsCredentialJsonLdContext } from '../../validators' import { W3cCredentialSchema } from './W3cCredentialSchema' -import { W3cCredentialStatus } from './W3cCredentialStatus' -import { IsW3cCredentialSubject, W3cCredentialSubject, W3cCredentialSubjectTransformer } from './W3cCredentialSubject' +import { IsW3cCredentialSubject, W3cCredentialSubject, W3cCredentialSubjectOptions, W3cCredentialSubjectTransformer } from './W3cCredentialSubject' import { IsW3cIssuer, W3cIssuer, W3cIssuerTransformer } from './W3cIssuer' +import { W3cCredentialStatus } from './w3c-credential-status/W3cCredentialStatus' export interface W3cCredentialOptions { context?: Array diff --git a/packages/core/src/modules/vc/models/credential/W3cCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/W3cCredentialStatus.ts deleted file mode 100644 index cf1de83151..0000000000 --- a/packages/core/src/modules/vc/models/credential/W3cCredentialStatus.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { IsString } from 'class-validator' - -import { IsUri } from '../../../../utils/validators' - -export interface W3cCredentialStatusOptions { - id: string - type: string -} - -export class W3cCredentialStatus { - public constructor(options: W3cCredentialStatusOptions) { - if (options) { - this.id = options.id - this.type = options.type - } - } - - @IsUri() - public id!: string - - @IsString() - public type!: string -} diff --git a/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts b/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts index fa43911d43..bfe8500acc 100644 --- a/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts @@ -1,3 +1,4 @@ +import type { W3cCredentialStatusOptions } from './w3c-credential-status/W3cCredentialStatus' import type { JsonObject } from '../../../../types' import type { SingleOrArray } from '../../../../utils' @@ -9,5 +10,6 @@ export interface W3cJsonCredential { issuanceDate: string expirationDate?: string credentialSubject: SingleOrArray + credentialStatus?: SingleOrArray [key: string]: unknown } diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts new file mode 100644 index 0000000000..ddb4c0b22a --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts @@ -0,0 +1,35 @@ +import { IsEnum, IsString } from 'class-validator' + +import { IsUri } from '../../../../../utils/validators' +import { BitStringStatusListEntry } from './bitstring-status-list'; + +export interface W3cCredentialStatusOptions { + id: string + type: string +} + +export type CredentialStatusBasedOnType = W3cCredentialStatus extends { type: 'BitstringStatusListEntry' } + ? BitStringStatusListEntry + : W3cCredentialStatus | BitStringStatusListEntry + +export enum W3cCredentialStatusSupportedTypes { + BitstringStatusListEntry = 'BitstringStatusListEntry', +} + +export class W3cCredentialStatus { + public constructor(options: W3cCredentialStatusOptions) { + if (options) { + this.id = options.id + this.type = options.type + } + } + + @IsUri() + @IsString() + public id!: string + + @IsString() + // @IsEnum(W3cCredentialStatusSupportedTypes, { message: 'Invalid credential status type' }) + public type!: string +} + diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts new file mode 100644 index 0000000000..c050d90b93 --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts @@ -0,0 +1,131 @@ +import { Type } from 'class-transformer' +import { + IsNumberString, + IsString, + registerDecorator, + ValidationArguments, + ValidationOptions, +} from 'class-validator' + +import { W3cCredentialStatus } from '../W3cCredentialStatus' +import { BitstringStatusListCredentialStatusPurpose } from './BitStringStatusListCredential' +import { JsonTransformer } from '../../../../../../utils' + + +export interface BitStringStatusListMessageOptions { + // a string representing the hexadecimal value of the status prefixed with 0x + status: string + // a string used by software developers to assist with debugging which SHOULD NOT be displayed to end users + message?: string + [key: string]: unknown +} + +export class BitStringStatusListMessage { + public constructor(options: BitStringStatusListMessageOptions) { + if (options) { + this.status = options.status + this.message = options.message + } + } + + @IsString() + public status!: string + + @IsString() + public message?: string; + + [key: string]: unknown | undefined +} + +/** + * Status list Entry, used to check status of a credential being issued or verified during presentaton. + * + * @see https://www.w3.org/TR/vc-bitstring-status-list/#bitstringstatuslistentry + */ +export class BitStringStatusListEntry extends W3cCredentialStatus { + public static type = 'BitstringStatusListEntry' + + public constructor(options: IBitStringStatusListEntryOptions) { + super({ id: options.id, type: BitStringStatusListEntry.type }) + + if (options) { + this.statusPurpose = options.statusPurpose + this.statusListCredential = options.statusListCredential + this.statusListIndex = options.statusListIndex + + if (options.statusSize) this.statusSize = options.statusSize + if (options.statusMessage) this.statusMessage = options.statusMessage + } + } + + @IsSupportedStatusPurpose({ message: 'Invalid statusPurpose value' }) + public statusPurpose!: string + + @IsString() + public statusListIndex!: string + + @IsString() + public statusListCredential!: string + + @IsNumberString() + public statusSize?: string + + @Type(() => BitStringStatusListMessage) + public statusMessage?: BitStringStatusListStatusMessage[] + + + public static fromJson(json: Record) { + return JsonTransformer.fromJSON(json, BitStringStatusListEntry) + } +} + +export interface BitStringStatusListStatusMessage { + // a string representing the hexadecimal value of the status prefixed with 0x + status: string + // a string used by software developers to assist with debugging which SHOULD NOT be displayed to end users + message?: string + // We can have some key value pairs as well + [key: string]: unknown +} + +export interface IBitStringStatusListEntryOptions { + id: string + type: string + statusPurpose: string + // Unique identifier for specific credential + statusListIndex: string + // Must be url referencing to a VC of type 'BitstringStatusListCredential' + statusListCredential: string + // The statusSize indicates the size of the status entry in bits + statusSize?: string + // Must be preset if statusPurpose is message + /** + * the length of which MUST equal the number of possible status messages indicated by statusSize + * (e.g., statusMessage array MUST have 2 elements if statusSize has 1 bit, + * 4 elements if statusSize has 2 bits, 8 elements if statusSize has 3 bits, etc.). + */ + statusMessage?: BitStringStatusListStatusMessage[] + // An implementer MAY include the statusReference property. If present, its value MUST be a URL or an array of URLs [URL] which dereference to material related to the status + statusReference?: string | string[] +} + +export function IsSupportedStatusPurpose(validationOptions?: ValidationOptions) { + return function (object: Object, propertyName: string) { + registerDecorator({ + name: 'isSupportedStatusPurpose', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + validator: { + validate(value: any, args: ValidationArguments) { + return Object.values(BitstringStatusListCredentialStatusPurpose).includes(value) + }, + defaultMessage(args: ValidationArguments) { + return `The statusPurpose must be one of the following: ${Object.values( + BitstringStatusListCredentialStatusPurpose + ).join(', ')}` + }, + }, + }) + } +} diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusListCredential.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusListCredential.ts new file mode 100644 index 0000000000..cfcd42b7dc --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusListCredential.ts @@ -0,0 +1,232 @@ +import { Transform, TransformationType } from 'class-transformer' +import { buildMessage, IsEnum, isInstance, IsString, registerDecorator, ValidateBy, ValidationArguments, ValidationOptions } from 'class-validator' + +import { W3cCredential, W3cCredentialOptions } from '../../W3cCredential' +import { W3cCredentialSubject, W3cCredentialSubjectOptions } from '../../index' +import { mapSingleOrArray, SingleOrArray } from '../../../../../../utils' +import { CredoError } from '../../../../../../error' +import { IsSupportedStatusPurpose } from './BitStringStatusList' + +// The purpose can be anything apart from this as well +export enum BitstringStatusListCredentialStatusPurpose { + Revocation = 'revocation', + Suspension = 'suspension', +} + +// Define an interface for `credentialSubject` +export interface BitStringStatusListCredentialOptions extends W3cCredentialOptions { + credentialSubject: SingleOrArray +} + +// Define an interface for `credentialSubject` +export interface BitStringStatusListCredentialSubjectOptions extends W3cCredentialSubjectOptions { + type: string + statusPurpose: string + encodedList: string +} + +export class BitStringStatusListCredentialSubject extends W3cCredentialSubject { + public constructor(options: BitStringStatusListCredentialSubjectOptions) { + super({ + id: options.id, + claims: options.claims, + }) + + this.type = 'BitstringStatusList' + this.statusPurpose = options.statusPurpose + this.encodedList = options.encodedList + } + + @IsString() + public type!: string + + @IsSupportedStatusPurpose() + public statusPurpose!: string + + @IsString() + public encodedList!: string +} + + +/** + * StatusListCredential describes the format of the verifiable credential that encapsulates the status list. + * + * @see https://www.w3.org/TR/vc-bitstring-status-list/#bitstringstatuslistcredential + */ +export class BitStringStatusListCredential extends W3cCredential { + public static baseType = 'BitstringStatusListCredential' + public static defaultTypes = ['VerifiableCredential', BitStringStatusListCredential.baseType] + public constructor(options: BitStringStatusListCredentialOptions) { + super({ + ...options, + type: BitStringStatusListCredential.ensureTypes(options.type), + }) + + if (options) { + this.credentialSubject = mapSingleOrArray(options.credentialSubject, (subject) => + subject instanceof BitStringStatusListCredentialSubject + ? subject + : new BitStringStatusListCredentialSubject(subject) + ) + } + } + + @IsBitStringStatusListCredentialSubject({ each: true }) + @BitStringStatusListCredentialSubjectTransformer() + public credentialSubject!: SingleOrArray + + /** + * Ensures that the type array includes 'VerifiableCredential' and 'BitstringStatusListCredential'. + * @param types The input types array. + * @returns A types array guaranteed to include the required values. + */ + private static ensureTypes(types?: string[]): string[] { + const requiredTypes = new Set(BitStringStatusListCredential.defaultTypes) + const finalTypes = new Set(types ?? []) + requiredTypes.forEach((type) => finalTypes.add(type)) + return Array.from(finalTypes) + } +} + +export type W3cCredentialSubjectBase = W3cCredentialSubject extends { type: 'BitstringStatusList' } + ? BitStringStatusListCredentialSubject + : W3cCredentialSubject | BitStringStatusListCredentialSubject + +export function BitStringStatusListCredentialSubjectTransformer() { + return Transform( + ({ + value, + type: transformationType, + }: { + value: SingleOrArray + type: TransformationType + }) => { + if (transformationType === TransformationType.PLAIN_TO_CLASS) { + const vToClass = (v: unknown) => { + if (!v || typeof v !== 'object') { + throw new CredoError('Invalid credential subject') + } + if (isInstance(v, BitStringStatusListCredentialSubject)) { + return v + } + + const { id, type, claims, statusPurpose, encodedList } = v as Record + + if (id !== undefined && typeof id !== 'string') { + throw new CredoError('Invalid credential subject id') + } + + if (typeof statusPurpose !== 'string' || typeof encodedList !== 'string') { + throw new CredoError('Invalid credential subject properties') + } + + // Ensure claims is of the correct type + if (claims !== undefined && (typeof claims !== 'object' || claims === null)) { + throw new CredoError('Invalid claims property') + } + + if (type !== undefined && typeof type !== 'string' && type !== 'BitstringStatusList') { + throw new CredoError('Invalid type property') + } + + return new BitStringStatusListCredentialSubject({ + id, + type: type as string, + claims: claims as Record | undefined, + statusPurpose: statusPurpose as string, + encodedList, + }) + } + + if (Array.isArray(value) && value.length === 0) { + throw new CredoError('At least one credential subject is required') + } + + return Array.isArray(value) ? value.map(vToClass) : vToClass(value) + } else if (transformationType === TransformationType.CLASS_TO_PLAIN) { + const vToJson = (v: unknown) => { + if (v instanceof BitStringStatusListCredentialSubject) { + const base = v.id ? { ...v.claims, id: v.id } : { ...v.claims } + return { + ...base, + statusPurpose: v.statusPurpose, + encodedList: v.encodedList, + } + } + return v + } + + return Array.isArray(value) ? value.map(vToJson) : vToJson(value) + } + // PLAIN_TO_PLAIN + return value + } + ) +} + +export function IsBitStringStatusListCredentialSubject(validationOptions?: ValidationOptions): PropertyDecorator { + return ValidateBy( + { + name: 'IsBitStringStatusListCredentialSubject', + validator: { + validate: (value): boolean => { + return isInstance(value, BitStringStatusListCredentialSubject) + }, + defaultMessage: buildMessage( + (eachPrefix) => + eachPrefix + '$property must be an object or an array of objects with an optional id property', + validationOptions + ), + }, + }, + validationOptions + ) +} + +/** + * Custom validator to check if the subject matches the `BitStringStatusListCredentialSubject` type. + */ +export function IsBitStringStatusListCredentialSubjectOrW3cCredentialSubject( + validationOptions?: ValidationOptions +) { + return function (object: Object, propertyName: string) { + registerDecorator({ + name: 'IsBitStringStatusListCredentialSubjectOrW3cCredentialSubject', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + validator: { + validate(value: any, args: ValidationArguments) { + if (Array.isArray(value)) { + return value.every( + (subject) => + subject instanceof BitStringStatusListCredentialSubject || subject instanceof W3cCredentialSubject + ) + } + return value instanceof BitStringStatusListCredentialSubject || value instanceof W3cCredentialSubject + }, + defaultMessage(args: ValidationArguments) { + return `The credentialSubject must either be a W3cCredentialSubject or BitStringStatusListCredentialSubject.`; + }, + }, + }); + }; +} + +/** + * Custom transformer to dynamically resolve and transform the subject type. + */ +export function CredentialSubjectTransformer() { + return Transform(({ value }) => { + if (Array.isArray(value)) { + return value.map((subject) => + (subject instanceof BitStringStatusListCredentialSubject && subject.type === 'BitstringStatusList') + ? new BitStringStatusListCredentialSubject(subject) + : new W3cCredentialSubject(subject) + ) + } + return (value instanceof BitStringStatusListCredentialSubject && value.type === 'BitstringStatusList' + ? new BitStringStatusListCredentialSubject(value) + : new W3cCredentialSubject(value)) + }); +} \ No newline at end of file diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts new file mode 100644 index 0000000000..9741da748d --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts @@ -0,0 +1,127 @@ +import type { BitStringStatusListEntry } from './BitStringStatusList' +import type { BitStringStatusListCredential } from './BitStringStatusListCredential' +import type { AgentContext } from '../../../../../../agent/context' + +import { inflate } from 'pako' + +import { CredoError } from '../../../../../../error' +import { W3cCredentialService } from '../../../../W3cCredentialService' +import { W3cJsonLdVerifyCredentialOptions, W3cJwtVerifyCredentialOptions } from '../../../../W3cCredentialServiceOptions' +import { ClaimFormat } from '../../../ClaimFormat' + +// Function to fetch and parse the bit string status list credential +const fetchBitStringStatusListCredential = async ( + agentContext: AgentContext, + url: string +): Promise => { + agentContext.config.logger.debug('Fetching BitStringStatusListCredential') + const response = await agentContext.config.agentDependencies.fetch(url) + + if (!response.ok) { + throw new CredoError(`Failed to fetch BitStringStatusListCredential status list. HTTP Status: ${response.status}`) + } + agentContext.config.logger.debug('BitStringStatusListCredential fetched successfully') + // Validate signature + + try { + agentContext.config.logger.debug('returning fetched BitStringStatusListCredential') + return (await response.json()) as BitStringStatusListCredential + } catch (error) { + throw new CredoError('Failed to parse the bit string status list credential') + } +} + +export const verifyBitStringCredentialStatus = async ( + credentialStatus: BitStringStatusListEntry, + agentContext: AgentContext, + credentialFormat: ClaimFormat.JwtVc | ClaimFormat.LdpVc +) => { + try { + if (Array.isArray(credentialStatus)) { + agentContext.config.logger.debug('Credential status type is array') + throw new CredoError( + 'Invalid credential status type. Currently only a single BitstringStatusListEntry is supported per credential' + ) + } + agentContext.config.logger.debug('Fetching BitString StatusList Credential for verifying BitStringCredentialStatus') + // Fetch the bit string status list credential + const bitStringStatusListCredential = await fetchBitStringStatusListCredential( + agentContext, + credentialStatus.statusListCredential + ) + agentContext.config.logger.debug( + 'Fetched BitString StatusList Credential for verifying BitStringCredentialStatus successfully' + ) + + if (Array.isArray(bitStringStatusListCredential.credentialSubject)) { + agentContext.config.logger.debug( + 'Credential subject type is array for the fetched Bitstring Status List Credential' + ) + throw new CredoError( + 'Invalid credential subject type. Currently only a single credentialSubject is supported per BitstringStatusListCredential' + ) + } + + agentContext.config.logger.debug( + `This is the fetched BSLC ${JSON.stringify(bitStringStatusListCredential, null, 2)}` + ) + + // verify signatures of the credential + const w3cCredentialService = agentContext.dependencyManager.resolve(W3cCredentialService) + + let result + if (credentialFormat === ClaimFormat.JwtVc){ + result = await w3cCredentialService.verifyCredential(agentContext, bitStringStatusListCredential as unknown as W3cJwtVerifyCredentialOptions) + } else if (credentialFormat === ClaimFormat.LdpVc){ + result = await w3cCredentialService.verifyCredential(agentContext, bitStringStatusListCredential as unknown as W3cJsonLdVerifyCredentialOptions) + } + if (result && !result.isValid) { + throw new CredoError(`Failed to validate credential, error = ${result.error}`) + } + + // Decode the encoded bit string + let decodedBitStringArray; + try { + const encodedBitString = bitStringStatusListCredential.credentialSubject.encodedList + // 3rd approach + const decodedBitString = expand(encodedBitString) + decodedBitStringArray = [...decodedBitString] + } catch(err) { + throw new CredoError('Error decoding Bitstring of fetched Bitstring StatusList Credential') + } + + const statusListIndex = Number(credentialStatus.statusListIndex) + + agentContext.config.logger.debug(`This is decodedBitString length: ${decodedBitStringArray.length}`) + + // Ensure the statusListIndex is within bounds + if (statusListIndex < 0 || statusListIndex >= decodedBitStringArray.length) { + throw new CredoError('Status list index is out of bounds') + } + + // Check if the credential is revoked + if (decodedBitStringArray[statusListIndex] === '1') { + // To do: The error can be updated once we add support for status messages + throw new CredoError( + `Credential at index ${credentialStatus.statusListIndex} is in ${bitStringStatusListCredential.credentialSubject.statusPurpose} state.` + ) + } + agentContext.config.logger.debug('BitStringStatusList Credential verified successfully') + + return true + } catch (err) { + throw new CredoError('Error verifying bitstring credential status', err) + } +} + +function expand(encodedList: string): string { + // Step 1: Decode Base64url (assuming no Multibase prefix, otherwise use multibase) + const compressedData = Buffer.from(encodedList, "base64url"); + // Step 2: Decompress using GZIP (Pako) + const decompressedData = inflate(compressedData); + // Step 3: Convert Uint8Array to Bitstring + return Array.from(decompressedData) + .map(byte => byte.toString(2).padStart(8, "0")) // Convert each byte to 8-bit binary + .join(""); // Join all bits into a string +} + diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/index.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/index.ts new file mode 100644 index 0000000000..70247d2ce5 --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/index.ts @@ -0,0 +1,3 @@ +export * from './BitStringStatusList' +export * from './VerifyBitStringCredentialStatus' +export * from './BitStringStatusListCredential' diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/index.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/index.ts new file mode 100644 index 0000000000..1698506379 --- /dev/null +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/index.ts @@ -0,0 +1 @@ +export * from './bitstring-status-list' diff --git a/packages/didcomm/src/modules/credentials/CredentialsApi.ts b/packages/didcomm/src/modules/credentials/CredentialsApi.ts index 65efc4ef0c..aaf11b0f09 100644 --- a/packages/didcomm/src/modules/credentials/CredentialsApi.ts +++ b/packages/didcomm/src/modules/credentials/CredentialsApi.ts @@ -59,7 +59,7 @@ export interface CredentialsApi { // Issue Credential Methods acceptCredential(options: AcceptCredentialOptions): Promise - // Revoke Credential Methods + // Send Credential revocation notification Methods sendRevocationNotification(options: SendRevocationNotificationOptions): Promise // out of band diff --git a/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts b/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts index 641306cf6f..f3f6144b7a 100644 --- a/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts +++ b/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts @@ -9,6 +9,7 @@ export interface JsonCredential { issuanceDate: string expirationDate?: string credentialSubject: SingleOrArray + credentialStatus?: SingleOrArray [key: string]: unknown } diff --git a/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts b/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts index df40979b0f..2ab22f300b 100644 --- a/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts +++ b/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts @@ -242,7 +242,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService options[field] !== undefined) if (foundFields.length > 0) { @@ -368,12 +368,17 @@ export class JsonLdCredentialFormatService implements CredentialFormatService Date: Mon, 7 Apr 2025 23:58:53 +0530 Subject: [PATCH 03/14] fix: rebasing conflicts Signed-off-by: Krishna Waske --- packages/core/src/modules/vc/models/credential/W3cCredential.ts | 1 - .../modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts | 1 + .../credentials/formats/jsonld/JsonLdCredentialFormatService.ts | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/modules/vc/models/credential/W3cCredential.ts b/packages/core/src/modules/vc/models/credential/W3cCredential.ts index 938fa3eb29..01c50d4554 100644 --- a/packages/core/src/modules/vc/models/credential/W3cCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cCredential.ts @@ -1,6 +1,5 @@ import type { ValidationOptions } from 'class-validator' import type { JsonObject } from '../../../../types' -import type { W3cCredentialSubjectOptions } from './W3cCredentialSubject' import type { W3cIssuerOptions } from './W3cIssuer' import { Expose, Type } from 'class-transformer' diff --git a/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts b/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts index f3f6144b7a..b0956f8b7f 100644 --- a/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts +++ b/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts @@ -1,5 +1,6 @@ import type { JsonObject, SingleOrArray, W3cIssuerOptions } from '@credo-ts/core' import type { CredentialFormat } from '../CredentialFormat' +import { CredentialStatusBasedOnType } from '@credo-ts/core/build/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus' export interface JsonCredential { '@context': Array | JsonObject diff --git a/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts b/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts index 2ab22f300b..ff868d5a10 100644 --- a/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts +++ b/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts @@ -34,6 +34,7 @@ import { W3cCredentialService, W3cJsonLdCredentialService, W3cJsonLdVerifiableCredential, + deepEquality, findVerificationMethodByKeyType, utils, } from '@credo-ts/core' From 60dd875b141b519d1750824e72a7f095dfdf84df Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Tue, 8 Apr 2025 00:05:24 +0530 Subject: [PATCH 04/14] fix: as discussed in the call we would need to keep the logic for revocation separated from credo Signed-off-by: Krishna Waske --- .../src/modules/vc/W3cCredentialService.ts | 30 ------------------- .../core/src/modules/vc/W3cCredentialsApi.ts | 7 ----- .../W3cJsonLdCredentialService.ts | 7 ----- .../vc/jwt-vc/W3cJwtCredentialService.ts | 7 ----- 4 files changed, 51 deletions(-) diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index ab0c732918..6accc2b65f 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -184,36 +184,6 @@ export class W3cCredentialService { return w3cCredentialRecord } - /** - * Revoke a credential by issuer - * associated with the credential record. - * - * @param credentialRecordId The id of the credential record for which to revoke the credential - * @returns Revoke credential notification message - * - */ - public async revokeCredential( - agentContext: AgentContext, - options: RevokeCredentialOptions - ) { - // Considering a revoker of a credential can be anyone apart from the issuer - // Not sure, if we need to get Credential record, as it might not be present for ayone apart from the issuer - // const credentialRecod = await this.getCredentialRecordById(agentContext, options.credentialRecordId) - // if (!credentialRecod) { - // throw new CredoError(`Credential with id ${options.credentialRecordId} not found`) - // } - - if (options.format === ClaimFormat.JwtVc) { - const revoked = await this.w3cJwtCredentialService.revokeCredential(agentContext, options) - return revoked as unknown as W3cVerifiableCredential - } else if (options.format === ClaimFormat.LdpVc) { - const revoked = await this.w3cJsonLdCredentialService.revokeCredential(agentContext, options) - return revoked as unknown as W3cVerifiableCredential - } else { - throw new CredoError(`Unsupported format in options. Format must be either 'jwt_vc' or 'ldp_vc'`) - } - } - public async removeCredentialRecord(agentContext: AgentContext, id: string) { await this.w3cCredentialRepository.deleteById(agentContext, id) } diff --git a/packages/core/src/modules/vc/W3cCredentialsApi.ts b/packages/core/src/modules/vc/W3cCredentialsApi.ts index d24cfef182..f2ce5b61de 100644 --- a/packages/core/src/modules/vc/W3cCredentialsApi.ts +++ b/packages/core/src/modules/vc/W3cCredentialsApi.ts @@ -47,13 +47,6 @@ export class W3cCredentialsApi { return this.w3cCredentialService.getCredentialRecordById(this.agentContext, id) } - // Revoke Credential Methods - public async revokeCredential( - options: RevokeCredentialOptions - ): Promise { - return this.w3cCredentialService.revokeCredential(this.agentContext, options) - } - public async findCredentialRecordsByQuery( query: Query, queryOptions?: QueryOptions diff --git a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts index e3b74c698a..8a3ed65628 100644 --- a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts +++ b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts @@ -332,13 +332,6 @@ export class W3cJsonLdCredentialService { return proof } - // temporarily disable no unused var - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async revokeCredential(_agentContext: AgentContext, _options: W3cJsonLdRevokeCredentialOptions) { - // revoke jwt cred - throw new CredoError(`Revocation support not implemented for JsonLd`) - } - public getVerificationMethodTypesByProofType(proofType: string): string[] { return this.signatureSuiteRegistry.getByProofType(proofType).verificationMethodTypes } diff --git a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts index 34c9c18f63..b753652323 100644 --- a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts +++ b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts @@ -547,11 +547,4 @@ export class W3cJwtCredentialService { return verificationMethod } - - // temporarily disable no unused var - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async revokeCredential(_agentContext: AgentContext, _options: W3cJwtRevokeCredentialOptions) { - // revoke jwt cred - throw new CredoError(`Revocation support not implemented for jwtVc`) - } } From 178ca904cec948005c6a7360927e68f37b4bac25 Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Tue, 8 Apr 2025 00:08:55 +0530 Subject: [PATCH 05/14] fix: as discussed in the call we would need to keep the logic for revocation separated from credo Signed-off-by: Krishna Waske --- .../src/modules/vc/W3cCredentialService.ts | 1 - .../modules/vc/W3cCredentialServiceOptions.ts | 21 ------------------- .../core/src/modules/vc/W3cCredentialsApi.ts | 1 - .../W3cJsonLdCredentialService.ts | 1 - .../vc/jwt-vc/W3cJwtCredentialService.ts | 1 - 5 files changed, 25 deletions(-) diff --git a/packages/core/src/modules/vc/W3cCredentialService.ts b/packages/core/src/modules/vc/W3cCredentialService.ts index 6accc2b65f..175a4ca3c6 100644 --- a/packages/core/src/modules/vc/W3cCredentialService.ts +++ b/packages/core/src/modules/vc/W3cCredentialService.ts @@ -22,7 +22,6 @@ import type { import { CredoError } from '../../error' import { injectable } from '../../plugins' -import { RevokeCredentialOptions } from './W3cCredentialServiceOptions' import { CREDENTIALS_CONTEXT_V1_URL } from './constants' import { W3cJsonLdVerifiableCredential } from './data-integrity' import { W3cJsonLdCredentialService } from './data-integrity/W3cJsonLdCredentialService' diff --git a/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts b/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts index de66fd563b..806a9f9d11 100644 --- a/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts +++ b/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts @@ -27,12 +27,6 @@ export type W3cSignPresentationOptions = - Format extends ClaimFormat.JwtVc - ? W3cJwtRevokeCredentialOptions - : Format extends ClaimFormat.LdpVc - ? W3cJsonLdRevokeCredentialOptions - : W3cJwtRevokeCredentialOptions | W3cJsonLdRevokeCredentialOptions interface W3cSignCredentialOptionsBase { /** @@ -197,18 +191,3 @@ export interface W3cJsonLdVerifyPresentationOptions extends W3cVerifyPresentatio export interface StoreCredentialOptions { credential: W3cVerifiableCredential } - -/** - * Interface for W3cCredentialsApi.revokeCredential. revoke a w3c credential. - */ -export interface RevokeCredentialOptionsBase { - credentialRecordId: string -} - -export interface W3cJwtRevokeCredentialOptions extends RevokeCredentialOptionsBase { - format: ClaimFormat.JwtVc -} - -export interface W3cJsonLdRevokeCredentialOptions extends RevokeCredentialOptionsBase { - format: ClaimFormat.LdpVc -} diff --git a/packages/core/src/modules/vc/W3cCredentialsApi.ts b/packages/core/src/modules/vc/W3cCredentialsApi.ts index f2ce5b61de..a5dfb6e6e0 100644 --- a/packages/core/src/modules/vc/W3cCredentialsApi.ts +++ b/packages/core/src/modules/vc/W3cCredentialsApi.ts @@ -1,6 +1,5 @@ import type { Query, QueryOptions } from '../../storage/StorageService' import type { - RevokeCredentialOptions, StoreCredentialOptions, W3cCreatePresentationOptions, W3cSignCredentialOptions, diff --git a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts index 8a3ed65628..d80d1d4320 100644 --- a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts +++ b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts @@ -2,7 +2,6 @@ import type { AgentContext } from '../../../agent/context' import type { Key } from '../../../crypto/Key' import type { SingleOrArray } from '../../../utils' import type { - W3cJsonLdRevokeCredentialOptions, W3cJsonLdSignCredentialOptions, W3cJsonLdSignPresentationOptions, W3cJsonLdVerifyCredentialOptions, diff --git a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts index b753652323..dbcc95c5d8 100644 --- a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts +++ b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts @@ -2,7 +2,6 @@ import type { AgentContext } from '../../../agent/context' import type { VerifyJwsResult } from '../../../crypto/JwsService' import type { DidPurpose, VerificationMethod } from '../../dids' import type { - W3cJwtRevokeCredentialOptions, W3cJwtSignCredentialOptions, W3cJwtSignPresentationOptions, W3cJwtVerifyCredentialOptions, From 59f49ed2fc7b3a4ae37e7dc6de0b8f8efb38133a Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Tue, 8 Apr 2025 00:11:45 +0530 Subject: [PATCH 06/14] chore: remove comments Signed-off-by: Krishna Waske --- .../modules/vc/data-integrity/W3cJsonLdCredentialService.ts | 6 ------ .../core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts | 4 ---- 2 files changed, 10 deletions(-) diff --git a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts index d80d1d4320..9a4bb6afdc 100644 --- a/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts +++ b/packages/core/src/modules/vc/data-integrity/W3cJsonLdCredentialService.ts @@ -112,9 +112,6 @@ export class W3cJsonLdCredentialService { documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), checkStatus: async ({ credential }: { credential: W3cJsonCredential }) => { if (verifyCredentialStatus && credential.credentialStatus) { - // await verifyBitStringCredentialStatus(credential, agentContext) - // Add a verification function that then checks which type of credentailStatus we have - // If the type is supported, we validate it and return the result await validateStatus(credential.credentialStatus, agentContext, ClaimFormat.LdpVc) } return { @@ -271,9 +268,6 @@ export class W3cJsonLdCredentialService { documentLoader: this.w3cCredentialsModuleConfig.documentLoader(agentContext), checkStatus: async ({ credential }: { credential: W3cJsonCredential }) => { if (verifyCredentialStatus && credential.credentialStatus) { - // await verifyBitStringCredentialStatus(credential, agentContext) - // Add a verification function that then checks which type of credentailStatus we have - // If the type is supported, we validate it and return the result await validateStatus(credential.credentialStatus, agentContext, ClaimFormat.LdpVc) } return { diff --git a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts index dbcc95c5d8..1b0e4319f5 100644 --- a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts +++ b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts @@ -197,10 +197,6 @@ export class W3cJwtCredentialService { } } else if (verifyCredentialStatus && credential.credentialStatus) { // TODO: Add similar verification for JWT VCs - // validationResults.validations.credentialStatus = { - // isValid: false, - // error: new CredoError('Verifying credential status is not supported for JWT VCs'), - // } validationResults.validations.credentialStatus = { isValid: await validateStatus(credential.credentialStatus, agentContext, ClaimFormat.JwtVc) } From 748731e653a8f2d10d17d14883199c9c89d4c7cc Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Tue, 8 Apr 2025 00:20:02 +0530 Subject: [PATCH 07/14] fix: remove unnecessay imports Signed-off-by: Krishna Waske --- packages/core/src/modules/vc/W3cCredentialsApi.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/src/modules/vc/W3cCredentialsApi.ts b/packages/core/src/modules/vc/W3cCredentialsApi.ts index a5dfb6e6e0..71590b871f 100644 --- a/packages/core/src/modules/vc/W3cCredentialsApi.ts +++ b/packages/core/src/modules/vc/W3cCredentialsApi.ts @@ -14,8 +14,6 @@ import { AgentContext } from '../../agent' import { injectable } from '../../plugins' import { W3cCredentialService } from './W3cCredentialService' -import { W3cJsonLdVerifiableCredential } from './data-integrity' -import { W3cJwtVerifiableCredential } from './jwt-vc' /** * @public From 294192093cf65c7ec5581b6f3b8f939c7eadf401 Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Sat, 7 Jun 2025 16:12:42 +0530 Subject: [PATCH 08/14] fix: consistency with other interface Signed-off-by: Krishna Waske --- .../bitstring-status-list/BitStringStatusList.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts index c050d90b93..f65aa045db 100644 --- a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts @@ -45,7 +45,7 @@ export class BitStringStatusListMessage { export class BitStringStatusListEntry extends W3cCredentialStatus { public static type = 'BitstringStatusListEntry' - public constructor(options: IBitStringStatusListEntryOptions) { + public constructor(options: BitStringStatusListEntryOptions) { super({ id: options.id, type: BitStringStatusListEntry.type }) if (options) { @@ -88,7 +88,7 @@ export interface BitStringStatusListStatusMessage { [key: string]: unknown } -export interface IBitStringStatusListEntryOptions { +export interface BitStringStatusListEntryOptions { id: string type: string statusPurpose: string From 6fdad0fb5ec49e2f48ed89644e16ec2a3853d2c6 Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Sat, 7 Jun 2025 16:20:57 +0530 Subject: [PATCH 09/14] fix: expose CredentialStatusBasedOnType to easily access it Signed-off-by: Krishna Waske --- packages/core/src/modules/vc/models/credential/index.ts | 1 + .../vc/models/credential/w3c-credential-status/index.ts | 1 + .../credentials/formats/jsonld/JsonLdCredentialFormat.ts | 3 +-- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/modules/vc/models/credential/index.ts b/packages/core/src/modules/vc/models/credential/index.ts index d0118e7e07..9d7153aa1a 100644 --- a/packages/core/src/modules/vc/models/credential/index.ts +++ b/packages/core/src/modules/vc/models/credential/index.ts @@ -3,3 +3,4 @@ export * from './W3cCredentialSubject' export * from './W3cIssuer' export * from './W3cCredential' export * from './W3cVerifiableCredential' +export * from './w3c-credential-status' diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/index.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/index.ts index 1698506379..8eaae77a6e 100644 --- a/packages/core/src/modules/vc/models/credential/w3c-credential-status/index.ts +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/index.ts @@ -1 +1,2 @@ export * from './bitstring-status-list' +export * from './W3cCredentialStatus' diff --git a/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts b/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts index b0956f8b7f..fec62ae4ca 100644 --- a/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts +++ b/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormat.ts @@ -1,6 +1,5 @@ -import type { JsonObject, SingleOrArray, W3cIssuerOptions } from '@credo-ts/core' +import type { CredentialStatusBasedOnType, JsonObject, SingleOrArray, W3cIssuerOptions } from '@credo-ts/core' import type { CredentialFormat } from '../CredentialFormat' -import { CredentialStatusBasedOnType } from '@credo-ts/core/build/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus' export interface JsonCredential { '@context': Array | JsonObject From 5454eda8cb3516c186d778d7cebae12d18867622 Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Sat, 7 Jun 2025 16:24:51 +0530 Subject: [PATCH 10/14] chore: remove commented code Signed-off-by: Krishna Waske --- .../credential/w3c-credential-status/W3cCredentialStatus.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts index ddb4c0b22a..43d0a5da43 100644 --- a/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts @@ -29,7 +29,6 @@ export class W3cCredentialStatus { public id!: string @IsString() - // @IsEnum(W3cCredentialStatusSupportedTypes, { message: 'Invalid credential status type' }) public type!: string } From 8c2dc94ef0ba2161dba8491a210f6cd3c00a8a0d Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Mon, 23 Jun 2025 10:17:48 +0530 Subject: [PATCH 11/14] fix: import path change Signed-off-by: Krishna Waske --- .../modules/vc/data-integrity/libraries/credentialStatus.ts | 2 +- .../bitstring-status-list/BitStringStatusListCredential.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/modules/vc/data-integrity/libraries/credentialStatus.ts b/packages/core/src/modules/vc/data-integrity/libraries/credentialStatus.ts index 3c988342cc..012aa43a99 100644 --- a/packages/core/src/modules/vc/data-integrity/libraries/credentialStatus.ts +++ b/packages/core/src/modules/vc/data-integrity/libraries/credentialStatus.ts @@ -8,7 +8,7 @@ import { verifyBitStringCredentialStatus, } from '../../models/credential/w3c-credential-status' import { W3cCredentialStatusSupportedTypes } from '../../models/credential/w3c-credential-status/W3cCredentialStatus' -import { SingleOrArray } from '../../../../utils' +import type { SingleOrArray } from '../../../../types' import { ClaimFormat } from '../../models' // Function to validate the status using the updated method diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusListCredential.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusListCredential.ts index cfcd42b7dc..71bd70830d 100644 --- a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusListCredential.ts +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusListCredential.ts @@ -3,7 +3,8 @@ import { buildMessage, IsEnum, isInstance, IsString, registerDecorator, Validate import { W3cCredential, W3cCredentialOptions } from '../../W3cCredential' import { W3cCredentialSubject, W3cCredentialSubjectOptions } from '../../index' -import { mapSingleOrArray, SingleOrArray } from '../../../../../../utils' +import { mapSingleOrArray } from '../../../../../../utils' +import type { SingleOrArray } from '../../../../../../types' import { CredoError } from '../../../../../../error' import { IsSupportedStatusPurpose } from './BitStringStatusList' From 1100c634f27203353d6f6be2a05c67d789c2161c Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Mon, 23 Jun 2025 10:21:57 +0530 Subject: [PATCH 12/14] fix: add and fix existing test cases Signed-off-by: Krishna Waske --- .../W3cJsonLdCredentialService.test.ts | 63 +++++++++++++ .../vc/data-integrity/__tests__/fixtures.ts | 88 +++++++++++++++++++ 2 files changed, 151 insertions(+) diff --git a/packages/core/src/modules/vc/data-integrity/__tests__/W3cJsonLdCredentialService.test.ts b/packages/core/src/modules/vc/data-integrity/__tests__/W3cJsonLdCredentialService.test.ts index fbfbfe335c..9a0a60e1b8 100644 --- a/packages/core/src/modules/vc/data-integrity/__tests__/W3cJsonLdCredentialService.test.ts +++ b/packages/core/src/modules/vc/data-integrity/__tests__/W3cJsonLdCredentialService.test.ts @@ -130,6 +130,26 @@ describe('W3cJsonLdCredentialsService', () => { expect(asArray(vc.proof)[0].verificationMethod).toEqual(verificationMethod) }) + it('should return a successfully signed revocable bitstring credential', async () => { + const credentialJson = Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT_REVOCABLE + + const credential = JsonTransformer.fromJSON(credentialJson, W3cCredential) + + const vc = await w3cJsonLdCredentialService.signCredential(agentContext, { + format: ClaimFormat.LdpVc, + credential, + proofType: 'Ed25519Signature2018', + verificationMethod: verificationMethod, + }) + + expect(vc).toBeInstanceOf(W3cJsonLdVerifiableCredential) + expect(vc.issuer).toEqual(issuerDidKey.did) + expect(Array.isArray(vc.proof)).toBe(false) + expect(vc.proof).toBeInstanceOf(LinkedDataProof) + + expect(asArray(vc.proof)[0].verificationMethod).toEqual(verificationMethod) + }) + it('should throw because of verificationMethod does not belong to this wallet', async () => { const credentialJson = Ed25519Signature2018Fixtures.TEST_LD_DOCUMENT credentialJson.issuer = issuerDidKey.did @@ -265,6 +285,49 @@ describe('W3cJsonLdCredentialsService', () => { }) }) + describe('verifyCredential revocable', () => { + it('should verify a revocable credential successfully', async () => { + const vc = JsonTransformer.fromJSON( + Ed25519Signature2018Fixtures.TEST_LD_REVOCABLE_DOCUMENT_SIGNED, + W3cJsonLdVerifiableCredential + ) + + const result = await w3cJsonLdCredentialService.verifyCredential(agentContext, { credential: vc, verifyCredentialStatus: false }) + + expect(result).toEqual({ + isValid: true, + error: undefined, + validations: { + vcJs: { + isValid: true, + results: expect.any(Array), + log: [ + { + id: 'expiration', + valid: true, + }, + { + id: 'valid_signature', + valid: true, + }, + { + id: 'issuer_did_resolves', + valid: true, + }, + { + id: 'revocation_status', + valid: true, + }, + ], + statusResult: { + verified: true, + }, + }, + }, + }) + }) + // TODO: add more tests + }) describe('signPresentation', () => { it('should successfully create a presentation from single verifiable credential', async () => { const presentation = JsonTransformer.fromJSON(Ed25519Signature2018Fixtures.TEST_VP_DOCUMENT, W3cPresentation) diff --git a/packages/core/src/modules/vc/data-integrity/__tests__/fixtures.ts b/packages/core/src/modules/vc/data-integrity/__tests__/fixtures.ts index e06481460a..af67116b37 100644 --- a/packages/core/src/modules/vc/data-integrity/__tests__/fixtures.ts +++ b/packages/core/src/modules/vc/data-integrity/__tests__/fixtures.ts @@ -42,6 +42,27 @@ export const Ed25519Signature2018Fixtures = { birthDate: '1958-07-17', }, }, + TEST_LD_DOCUMENT_REVOCABLE: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + type: 'BachelorDegree', + name: 'Bachelor of Science and Arts', + }, + }, + credentialStatus: { + // TODO: change the url to mock retrieval of BSLC + id: "https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json#1", + type: "BitstringStatusListEntry", + statusListCredential: + "https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json", + statusListIndex: "0", + statusPurpose: "revocation", + }, + }, TEST_LD_DOCUMENT_SIGNED: { '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], @@ -83,6 +104,73 @@ export const Ed25519Signature2018Fixtures = { jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..Ej5aEUBTgeNm3_a4uO_AuNnisldnYTMMGMom4xLb-_TmoYe7467Yo046Bw2QqdfdBja6y-HBbBj4SonOlwswAg', }, }, + TEST_LD_REVOCABLE_DOCUMENT_SIGNED: { + "@context": [ + CREDENTIALS_CONTEXT_V1_URL, + "https://www.w3.org/2018/credentials/examples/v1" + ], + type: [ + "VerifiableCredential", + "UniversityDegreeCredential" + ], + issuer: "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + issuanceDate: "2017-10-22T12:23:48Z", + credentialSubject: { + degree: { + type: "BachelorDegree", + name: "Bachelor of Science and Arts" + } + }, + credentialStatus: { + id: "https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json#1", + type: "BitstringStatusListEntry", + statusListCredential: "https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json", + statusListIndex: "0", + statusPurpose: "revocation" + }, + proof: { + verificationMethod: "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + type: "Ed25519Signature2018", + created: "2025-06-23T04:40:28Z", + proofPurpose: "assertionMethod", + jws: "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..KgyaMnJHAC7p_6vdH7HO4yFRl6dwpPaK0yRG8uilaQVIuZfonB_v7io1HyApBUAS-cPSQ1p8ZrQDs-7kvCJtAg" + } + }, + + TEST_LD_REVOCABLE_DOCUMENT_BAD_SIGNED: { + "@context": [ + CREDENTIALS_CONTEXT_V1_URL, + "https://www.w3.org/2018/credentials/examples/v1" + ], + type: [ + "VerifiableCredential", + "UniversityDegreeCredential" + ], + issuer: "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + issuanceDate: "2017-10-22T12:23:48Z", + credentialSubject: { + degree: { + type: "BachelorDegree", + name: "Bachelor of Science and Arts" + } + }, + credentialStatus: { + id: "https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json#1", + type: "BitstringStatusListEntry", + statusListCredential: "https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json", + statusListIndex: "0", + statusPurpose: "revocation" + }, + proof: { + verificationMethod: "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + type: "Ed25519Signature2018", + created: "2025-06-23T04:40:28Z", + proofPurpose: "assertionMethod", + // Replaced 6th character 'G' with 'g' + jws: "eyJhbgciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..KgyaMnJHAC7p_6vdH7HO4yFRl6dwpPaK0yRG8uilaQVIuZfonB_v7io1HyApBUAS-cPSQ1p8ZrQDs-7kvCJtAg" + } + }, + TEST_VP_DOCUMENT: { '@context': [CREDENTIALS_CONTEXT_V1_URL], type: ['VerifiablePresentation'], From 05ea39ef6a6451bd89d120283c303830a8ebd99b Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Mon, 23 Jun 2025 10:54:36 +0530 Subject: [PATCH 13/14] fix: package lock Signed-off-by: Krishna Waske --- pnpm-lock.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c5ec5ad2c5..a6534f8e24 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -473,7 +473,10 @@ importers: version: 1.3.6 object-inspect: specifier: ^1.10.3 - version: 1.13.2 + version: 1.13.1 + pako: + specifier: ^2.1.0 + version: 2.1.0 query-string: specifier: ^7.0.1 version: 7.1.3 From 3ab1d73333a2e03805995c52d29094a416981244 Mon Sep 17 00:00:00 2001 From: Krishna Waske Date: Mon, 23 Jun 2025 11:24:33 +0530 Subject: [PATCH 14/14] fix: linting issues Signed-off-by: Krishna Waske --- .../W3cJsonLdCredentialService.test.ts | 5 +- .../vc/data-integrity/__tests__/fixtures.ts | 97 ++++++++----------- .../libraries/credentialStatus.ts | 8 +- .../vc/jwt-vc/W3cJwtCredentialService.ts | 11 ++- .../vc/models/credential/W3cCredential.ts | 7 +- .../vc/models/credential/W3cJsonCredential.ts | 4 +- .../W3cCredentialStatus.ts | 5 +- .../BitStringStatusList.ts | 22 ++--- .../BitStringStatusListCredential.ts | 54 ++++++----- .../VerifyBitStringCredentialStatus.ts | 59 ++++++----- .../jsonld/JsonLdCredentialFormatService.ts | 1 - 11 files changed, 144 insertions(+), 129 deletions(-) diff --git a/packages/core/src/modules/vc/data-integrity/__tests__/W3cJsonLdCredentialService.test.ts b/packages/core/src/modules/vc/data-integrity/__tests__/W3cJsonLdCredentialService.test.ts index 9a0a60e1b8..b34d248425 100644 --- a/packages/core/src/modules/vc/data-integrity/__tests__/W3cJsonLdCredentialService.test.ts +++ b/packages/core/src/modules/vc/data-integrity/__tests__/W3cJsonLdCredentialService.test.ts @@ -292,7 +292,10 @@ describe('W3cJsonLdCredentialsService', () => { W3cJsonLdVerifiableCredential ) - const result = await w3cJsonLdCredentialService.verifyCredential(agentContext, { credential: vc, verifyCredentialStatus: false }) + const result = await w3cJsonLdCredentialService.verifyCredential(agentContext, { + credential: vc, + verifyCredentialStatus: false, + }) expect(result).toEqual({ isValid: true, diff --git a/packages/core/src/modules/vc/data-integrity/__tests__/fixtures.ts b/packages/core/src/modules/vc/data-integrity/__tests__/fixtures.ts index af67116b37..b27a32750f 100644 --- a/packages/core/src/modules/vc/data-integrity/__tests__/fixtures.ts +++ b/packages/core/src/modules/vc/data-integrity/__tests__/fixtures.ts @@ -55,12 +55,11 @@ export const Ed25519Signature2018Fixtures = { }, credentialStatus: { // TODO: change the url to mock retrieval of BSLC - id: "https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json#1", - type: "BitstringStatusListEntry", - statusListCredential: - "https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json", - statusListIndex: "0", - statusPurpose: "revocation", + id: 'https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json#1', + type: 'BitstringStatusListEntry', + statusListCredential: 'https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json', + statusListIndex: '0', + statusPurpose: 'revocation', }, }, @@ -105,70 +104,60 @@ export const Ed25519Signature2018Fixtures = { }, }, TEST_LD_REVOCABLE_DOCUMENT_SIGNED: { - "@context": [ - CREDENTIALS_CONTEXT_V1_URL, - "https://www.w3.org/2018/credentials/examples/v1" - ], - type: [ - "VerifiableCredential", - "UniversityDegreeCredential" - ], - issuer: "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - issuanceDate: "2017-10-22T12:23:48Z", + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', credentialSubject: { degree: { - type: "BachelorDegree", - name: "Bachelor of Science and Arts" - } + type: 'BachelorDegree', + name: 'Bachelor of Science and Arts', + }, }, credentialStatus: { - id: "https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json#1", - type: "BitstringStatusListEntry", - statusListCredential: "https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json", - statusListIndex: "0", - statusPurpose: "revocation" + id: 'https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json#1', + type: 'BitstringStatusListEntry', + statusListCredential: 'https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json', + statusListIndex: '0', + statusPurpose: 'revocation', }, proof: { - verificationMethod: "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - type: "Ed25519Signature2018", - created: "2025-06-23T04:40:28Z", - proofPurpose: "assertionMethod", - jws: "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..KgyaMnJHAC7p_6vdH7HO4yFRl6dwpPaK0yRG8uilaQVIuZfonB_v7io1HyApBUAS-cPSQ1p8ZrQDs-7kvCJtAg" - } + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + type: 'Ed25519Signature2018', + created: '2025-06-23T04:40:28Z', + proofPurpose: 'assertionMethod', + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..KgyaMnJHAC7p_6vdH7HO4yFRl6dwpPaK0yRG8uilaQVIuZfonB_v7io1HyApBUAS-cPSQ1p8ZrQDs-7kvCJtAg', + }, }, TEST_LD_REVOCABLE_DOCUMENT_BAD_SIGNED: { - "@context": [ - CREDENTIALS_CONTEXT_V1_URL, - "https://www.w3.org/2018/credentials/examples/v1" - ], - type: [ - "VerifiableCredential", - "UniversityDegreeCredential" - ], - issuer: "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - issuanceDate: "2017-10-22T12:23:48Z", + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', credentialSubject: { degree: { - type: "BachelorDegree", - name: "Bachelor of Science and Arts" - } + type: 'BachelorDegree', + name: 'Bachelor of Science and Arts', + }, }, credentialStatus: { - id: "https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json#1", - type: "BitstringStatusListEntry", - statusListCredential: "https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json", - statusListIndex: "0", - statusPurpose: "revocation" + id: 'https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json#1', + type: 'BitstringStatusListEntry', + statusListCredential: 'https://ghkrishna.github.io/schemas/revocationSchemas/signed1.json', + statusListIndex: '0', + statusPurpose: 'revocation', }, proof: { - verificationMethod: "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - type: "Ed25519Signature2018", - created: "2025-06-23T04:40:28Z", - proofPurpose: "assertionMethod", + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + type: 'Ed25519Signature2018', + created: '2025-06-23T04:40:28Z', + proofPurpose: 'assertionMethod', // Replaced 6th character 'G' with 'g' - jws: "eyJhbgciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..KgyaMnJHAC7p_6vdH7HO4yFRl6dwpPaK0yRG8uilaQVIuZfonB_v7io1HyApBUAS-cPSQ1p8ZrQDs-7kvCJtAg" - } + jws: 'eyJhbgciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..KgyaMnJHAC7p_6vdH7HO4yFRl6dwpPaK0yRG8uilaQVIuZfonB_v7io1HyApBUAS-cPSQ1p8ZrQDs-7kvCJtAg', + }, }, TEST_VP_DOCUMENT: { diff --git a/packages/core/src/modules/vc/data-integrity/libraries/credentialStatus.ts b/packages/core/src/modules/vc/data-integrity/libraries/credentialStatus.ts index 012aa43a99..2947113118 100644 --- a/packages/core/src/modules/vc/data-integrity/libraries/credentialStatus.ts +++ b/packages/core/src/modules/vc/data-integrity/libraries/credentialStatus.ts @@ -1,15 +1,14 @@ import type { AgentContext } from '../../../../agent' import type { W3cCredentialStatus } from '../../models/credential/w3c-credential-status/W3cCredentialStatus' - import { CredoError } from '../../../../error/CredoError' +import type { SingleOrArray } from '../../../../types' +import { ClaimFormat } from '../../models' import { BitStringStatusListEntry, verifyBitStringCredentialStatus, } from '../../models/credential/w3c-credential-status' import { W3cCredentialStatusSupportedTypes } from '../../models/credential/w3c-credential-status/W3cCredentialStatus' -import type { SingleOrArray } from '../../../../types' -import { ClaimFormat } from '../../models' // Function to validate the status using the updated method export const validateStatus = async ( @@ -17,7 +16,6 @@ export const validateStatus = async ( agentContext: AgentContext, credentialFormat: ClaimFormat.JwtVc | ClaimFormat.LdpVc ): Promise => { - if (Array.isArray(credentialStatus)) { agentContext.config.logger.debug('Credential status type is array') throw new CredoError( @@ -35,7 +33,7 @@ export const validateStatus = async ( credentialFormat ) } catch (errors) { - throw new CredoError(`Error while validating credential status`, errors) + throw new CredoError('Error while validating credential status', errors) } break default: diff --git a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts index 1de148696a..f98cdc2403 100644 --- a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts +++ b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts @@ -7,7 +7,12 @@ import type { W3cJwtVerifyCredentialOptions, W3cJwtVerifyPresentationOptions, } from '../W3cCredentialServiceOptions' -import { ClaimFormat, type SingleValidationResult, type W3cVerifyCredentialResult, type W3cVerifyPresentationResult } from '../models' +import { + ClaimFormat, + type SingleValidationResult, + type W3cVerifyCredentialResult, + type W3cVerifyPresentationResult, +} from '../models' import { JwsService } from '../../../crypto' import { CredoError } from '../../../error' @@ -21,11 +26,11 @@ import { getSupportedVerificationMethodTypesForPublicJwk, } from '../../dids/domain/key-type/keyDidMapping' import { KnownJwaSignatureAlgorithm, PublicJwk } from '../../kms' +import { validateStatus } from '../data-integrity/libraries/credentialStatus' import { W3cJwtVerifiableCredential } from './W3cJwtVerifiableCredential' import { W3cJwtVerifiablePresentation } from './W3cJwtVerifiablePresentation' import { getJwtPayloadFromCredential } from './credentialTransformer' import { getJwtPayloadFromPresentation } from './presentationTransformer' -import { validateStatus } from '../data-integrity/libraries/credentialStatus' /** * Supports signing and verification of credentials according to the [Verifiable Credential Data Model](https://www.w3.org/TR/vc-data-model) @@ -200,7 +205,7 @@ export class W3cJwtCredentialService { } else if (verifyCredentialStatus && credential.credentialStatus) { // TODO: Add similar verification for JWT VCs validationResults.validations.credentialStatus = { - isValid: await validateStatus(credential.credentialStatus, agentContext, ClaimFormat.JwtVc) + isValid: await validateStatus(credential.credentialStatus, agentContext, ClaimFormat.JwtVc), } } diff --git a/packages/core/src/modules/vc/models/credential/W3cCredential.ts b/packages/core/src/modules/vc/models/credential/W3cCredential.ts index eee54ab147..d1d2dc6fad 100644 --- a/packages/core/src/modules/vc/models/credential/W3cCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cCredential.ts @@ -11,7 +11,12 @@ import { CREDENTIALS_CONTEXT_V1_URL, VERIFIABLE_CREDENTIAL_TYPE } from '../../co import { IsCredentialJsonLdContext } from '../../validators' import { W3cCredentialSchema } from './W3cCredentialSchema' -import { IsW3cCredentialSubject, W3cCredentialSubject, W3cCredentialSubjectOptions, W3cCredentialSubjectTransformer } from './W3cCredentialSubject' +import { + IsW3cCredentialSubject, + W3cCredentialSubject, + W3cCredentialSubjectOptions, + W3cCredentialSubjectTransformer, +} from './W3cCredentialSubject' import { IsW3cIssuer, W3cIssuer, W3cIssuerTransformer } from './W3cIssuer' import { W3cCredentialStatus } from './w3c-credential-status/W3cCredentialStatus' diff --git a/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts b/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts index bfe8500acc..47bb8e8f96 100644 --- a/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts +++ b/packages/core/src/modules/vc/models/credential/W3cJsonCredential.ts @@ -1,6 +1,6 @@ +import type { JsonObject, SingleOrArray } from '../../../../types' + import type { W3cCredentialStatusOptions } from './w3c-credential-status/W3cCredentialStatus' -import type { JsonObject } from '../../../../types' -import type { SingleOrArray } from '../../../../utils' export interface W3cJsonCredential { '@context': Array diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts index 43d0a5da43..3c06166a26 100644 --- a/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/W3cCredentialStatus.ts @@ -1,7 +1,7 @@ -import { IsEnum, IsString } from 'class-validator' +import { IsString } from 'class-validator' import { IsUri } from '../../../../../utils/validators' -import { BitStringStatusListEntry } from './bitstring-status-list'; +import { BitStringStatusListEntry } from './bitstring-status-list' export interface W3cCredentialStatusOptions { id: string @@ -31,4 +31,3 @@ export class W3cCredentialStatus { @IsString() public type!: string } - diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts index f65aa045db..668e87dff0 100644 --- a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusList.ts @@ -1,16 +1,9 @@ -import { Type } from 'class-transformer' -import { - IsNumberString, - IsString, - registerDecorator, - ValidationArguments, - ValidationOptions, -} from 'class-validator' +import { IsNumberString, IsString, ValidationArguments, ValidationOptions, registerDecorator } from 'class-validator' +import { Type } from 'class-transformer' +import { JsonTransformer } from '../../../../../../utils' import { W3cCredentialStatus } from '../W3cCredentialStatus' import { BitstringStatusListCredentialStatusPurpose } from './BitStringStatusListCredential' -import { JsonTransformer } from '../../../../../../utils' - export interface BitStringStatusListMessageOptions { // a string representing the hexadecimal value of the status prefixed with 0x @@ -73,7 +66,6 @@ export class BitStringStatusListEntry extends W3cCredentialStatus { @Type(() => BitStringStatusListMessage) public statusMessage?: BitStringStatusListStatusMessage[] - public static fromJson(json: Record) { return JsonTransformer.fromJSON(json, BitStringStatusListEntry) } @@ -110,17 +102,19 @@ export interface BitStringStatusListEntryOptions { } export function IsSupportedStatusPurpose(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { + // biome-ignore lint/complexity/noBannedTypes: + return (object: Object, propertyName: string) => { registerDecorator({ name: 'isSupportedStatusPurpose', target: object.constructor, propertyName: propertyName, options: validationOptions, validator: { - validate(value: any, args: ValidationArguments) { + // biome-ignore lint/suspicious/noExplicitAny: + validate(value: any, _args: ValidationArguments) { return Object.values(BitstringStatusListCredentialStatusPurpose).includes(value) }, - defaultMessage(args: ValidationArguments) { + defaultMessage(_args: ValidationArguments) { return `The statusPurpose must be one of the following: ${Object.values( BitstringStatusListCredentialStatusPurpose ).join(', ')}` diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusListCredential.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusListCredential.ts index 71bd70830d..a5807eaf61 100644 --- a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusListCredential.ts +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/BitStringStatusListCredential.ts @@ -1,11 +1,19 @@ import { Transform, TransformationType } from 'class-transformer' -import { buildMessage, IsEnum, isInstance, IsString, registerDecorator, ValidateBy, ValidationArguments, ValidationOptions } from 'class-validator' - +import { + IsString, + ValidateBy, + ValidationArguments, + ValidationOptions, + buildMessage, + isInstance, + registerDecorator, +} from 'class-validator' import { W3cCredential, W3cCredentialOptions } from '../../W3cCredential' import { W3cCredentialSubject, W3cCredentialSubjectOptions } from '../../index' -import { mapSingleOrArray } from '../../../../../../utils' -import type { SingleOrArray } from '../../../../../../types' + import { CredoError } from '../../../../../../error' +import type { SingleOrArray } from '../../../../../../types' +import { mapSingleOrArray } from '../../../../../../utils' import { IsSupportedStatusPurpose } from './BitStringStatusList' // The purpose can be anything apart from this as well @@ -48,7 +56,6 @@ export class BitStringStatusListCredentialSubject extends W3cCredentialSubject { public encodedList!: string } - /** * StatusListCredential describes the format of the verifiable credential that encapsulates the status list. * @@ -84,7 +91,9 @@ export class BitStringStatusListCredential extends W3cCredential { private static ensureTypes(types?: string[]): string[] { const requiredTypes = new Set(BitStringStatusListCredential.defaultTypes) const finalTypes = new Set(types ?? []) - requiredTypes.forEach((type) => finalTypes.add(type)) + for (const presentType of requiredTypes) { + finalTypes.add(presentType) + } return Array.from(finalTypes) } } @@ -144,7 +153,8 @@ export function BitStringStatusListCredentialSubjectTransformer() { } return Array.isArray(value) ? value.map(vToClass) : vToClass(value) - } else if (transformationType === TransformationType.CLASS_TO_PLAIN) { + } + if (transformationType === TransformationType.CLASS_TO_PLAIN) { const vToJson = (v: unknown) => { if (v instanceof BitStringStatusListCredentialSubject) { const base = v.id ? { ...v.claims, id: v.id } : { ...v.claims } @@ -175,7 +185,7 @@ export function IsBitStringStatusListCredentialSubject(validationOptions?: Valid }, defaultMessage: buildMessage( (eachPrefix) => - eachPrefix + '$property must be an object or an array of objects with an optional id property', + `${eachPrefix}$property must be an object or an array of objects with an optional id property`, validationOptions ), }, @@ -187,17 +197,17 @@ export function IsBitStringStatusListCredentialSubject(validationOptions?: Valid /** * Custom validator to check if the subject matches the `BitStringStatusListCredentialSubject` type. */ -export function IsBitStringStatusListCredentialSubjectOrW3cCredentialSubject( - validationOptions?: ValidationOptions -) { - return function (object: Object, propertyName: string) { +export function IsBitStringStatusListCredentialSubjectOrW3cCredentialSubject(validationOptions?: ValidationOptions) { + // biome-ignore lint/complexity/noBannedTypes: + return (object: Object, propertyName: string) => { registerDecorator({ name: 'IsBitStringStatusListCredentialSubjectOrW3cCredentialSubject', target: object.constructor, propertyName: propertyName, options: validationOptions, validator: { - validate(value: any, args: ValidationArguments) { + // biome-ignore lint/suspicious/noExplicitAny: + validate(value: any, _args: ValidationArguments) { if (Array.isArray(value)) { return value.every( (subject) => @@ -206,12 +216,12 @@ export function IsBitStringStatusListCredentialSubjectOrW3cCredentialSubject( } return value instanceof BitStringStatusListCredentialSubject || value instanceof W3cCredentialSubject }, - defaultMessage(args: ValidationArguments) { - return `The credentialSubject must either be a W3cCredentialSubject or BitStringStatusListCredentialSubject.`; + defaultMessage(_args: ValidationArguments) { + return 'The credentialSubject must either be a W3cCredentialSubject or BitStringStatusListCredentialSubject.' }, }, - }); - }; + }) + } } /** @@ -221,13 +231,13 @@ export function CredentialSubjectTransformer() { return Transform(({ value }) => { if (Array.isArray(value)) { return value.map((subject) => - (subject instanceof BitStringStatusListCredentialSubject && subject.type === 'BitstringStatusList') + subject instanceof BitStringStatusListCredentialSubject && subject.type === 'BitstringStatusList' ? new BitStringStatusListCredentialSubject(subject) : new W3cCredentialSubject(subject) ) } - return (value instanceof BitStringStatusListCredentialSubject && value.type === 'BitstringStatusList' + return value instanceof BitStringStatusListCredentialSubject && value.type === 'BitstringStatusList' ? new BitStringStatusListCredentialSubject(value) - : new W3cCredentialSubject(value)) - }); -} \ No newline at end of file + : new W3cCredentialSubject(value) + }) +} diff --git a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts index 9741da748d..6ac2cf0165 100644 --- a/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts +++ b/packages/core/src/modules/vc/models/credential/w3c-credential-status/bitstring-status-list/VerifyBitStringCredentialStatus.ts @@ -1,13 +1,16 @@ -import type { BitStringStatusListEntry } from './BitStringStatusList' -import type { BitStringStatusListCredential } from './BitStringStatusListCredential' -import type { AgentContext } from '../../../../../../agent/context' +import { + W3cJsonLdVerifyCredentialOptions, + W3cJwtVerifyCredentialOptions, +} from '../../../../W3cCredentialServiceOptions' import { inflate } from 'pako' - +import type { AgentContext } from '../../../../../../agent/context' import { CredoError } from '../../../../../../error' import { W3cCredentialService } from '../../../../W3cCredentialService' -import { W3cJsonLdVerifyCredentialOptions, W3cJwtVerifyCredentialOptions } from '../../../../W3cCredentialServiceOptions' import { ClaimFormat } from '../../../ClaimFormat' +import { W3cVerifyCredentialResult } from '../../../W3cVerifyResult' +import type { BitStringStatusListEntry } from './BitStringStatusList' +import type { BitStringStatusListCredential } from './BitStringStatusListCredential' // Function to fetch and parse the bit string status list credential const fetchBitStringStatusListCredential = async ( @@ -27,7 +30,7 @@ const fetchBitStringStatusListCredential = async ( agentContext.config.logger.debug('returning fetched BitStringStatusListCredential') return (await response.json()) as BitStringStatusListCredential } catch (error) { - throw new CredoError('Failed to parse the bit string status list credential') + throw new CredoError('Failed to parse the bit string status list credential', { cause: error }) } } @@ -69,25 +72,35 @@ export const verifyBitStringCredentialStatus = async ( // verify signatures of the credential const w3cCredentialService = agentContext.dependencyManager.resolve(W3cCredentialService) - let result - if (credentialFormat === ClaimFormat.JwtVc){ - result = await w3cCredentialService.verifyCredential(agentContext, bitStringStatusListCredential as unknown as W3cJwtVerifyCredentialOptions) - } else if (credentialFormat === ClaimFormat.LdpVc){ - result = await w3cCredentialService.verifyCredential(agentContext, bitStringStatusListCredential as unknown as W3cJsonLdVerifyCredentialOptions) + let result: W3cVerifyCredentialResult + if (credentialFormat === ClaimFormat.JwtVc) { + result = await w3cCredentialService.verifyCredential( + agentContext, + bitStringStatusListCredential as unknown as W3cJwtVerifyCredentialOptions + ) + } else if (credentialFormat === ClaimFormat.LdpVc) { + result = await w3cCredentialService.verifyCredential( + agentContext, + bitStringStatusListCredential as unknown as W3cJsonLdVerifyCredentialOptions + ) + } else { + throw new CredoError( + 'Unsupported credential type for BSLC. Credential must be either a W3cJsonLdVerifiableCredential or a W3cJwtVerifiableCredential' + ) } if (result && !result.isValid) { throw new CredoError(`Failed to validate credential, error = ${result.error}`) } // Decode the encoded bit string - let decodedBitStringArray; + let decodedBitStringArray: Array try { const encodedBitString = bitStringStatusListCredential.credentialSubject.encodedList // 3rd approach const decodedBitString = expand(encodedBitString) decodedBitStringArray = [...decodedBitString] - } catch(err) { - throw new CredoError('Error decoding Bitstring of fetched Bitstring StatusList Credential') + } catch (err) { + throw new CredoError('Error decoding Bitstring of fetched Bitstring StatusList Credential', { cause: err }) } const statusListIndex = Number(credentialStatus.statusListIndex) @@ -115,13 +128,13 @@ export const verifyBitStringCredentialStatus = async ( } function expand(encodedList: string): string { - // Step 1: Decode Base64url (assuming no Multibase prefix, otherwise use multibase) - const compressedData = Buffer.from(encodedList, "base64url"); - // Step 2: Decompress using GZIP (Pako) - const decompressedData = inflate(compressedData); - // Step 3: Convert Uint8Array to Bitstring - return Array.from(decompressedData) - .map(byte => byte.toString(2).padStart(8, "0")) // Convert each byte to 8-bit binary - .join(""); // Join all bits into a string + // Step 1: Decode Base64url (assuming no Multibase prefix, otherwise use multibase) + // biome-ignore lint/style/noRestrictedGlobals: + const compressedData = Buffer.from(encodedList, 'base64url') + // Step 2: Decompress using GZIP (Pako) + const decompressedData = inflate(compressedData) + // Step 3: Convert Uint8Array to Bitstring + return Array.from(decompressedData) + .map((byte) => byte.toString(2).padStart(8, '0')) // Convert each byte to 8-bit binary + .join('') // Join all bits into a string } - diff --git a/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts b/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts index ff868d5a10..0ceeeb2906 100644 --- a/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts +++ b/packages/didcomm/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts @@ -379,7 +379,6 @@ export class JsonLdCredentialFormatService implements CredentialFormatService