Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
"lru_map": "^0.4.1",
"make-error": "^1.3.6",
"object-inspect": "^1.10.3",
"pako": "^2.1.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does pako work fine in react native? How does it compare with other modules such as fflate? I'm asking this because we want core to be as smaller as possible and it looks like pako is a bit heavy.

Copy link
Contributor Author

@GHkrishna GHkrishna Jun 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, that's a good suggestion, I'm all in on keeping core as small as possible,
Pako did work fine with an android device.
I was not aware about fflate, will have a look at it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pako is also used by SD-JWT JS library, works in react native, and since we already depend on it is maybe not a bad choice. I have no idea on the size .

"query-string": "^7.0.1",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0",
"tsyringe": "^4.8.0",
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
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'

Expand All @@ -23,6 +23,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'
Expand Down Expand Up @@ -109,10 +110,9 @@ 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 validateStatus(credential.credentialStatus, agentContext, ClaimFormat.LdpVc)
}
return {
verified: true,
Expand Down Expand Up @@ -259,12 +259,21 @@ export class W3cJsonLdCredentialService {
)
const allSuites = presentationSuites.concat(...credentialSuites)

const verifyCredentialStatus = options.verifyCredentialStatus ?? true
const verifyOptions: Record<string, unknown> = {
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 validateStatus(credential.credentialStatus, agentContext, ClaimFormat.LdpVc)
}
return {
verified: true,
}
},
}

// this is a hack because vcjs throws if purpose is passed as undefined or null
Expand Down
Original file line number Diff line number Diff line change
@@ -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<W3cCredentialStatus>,
agentContext: AgentContext,
credentialFormat: ClaimFormat.JwtVc | ClaimFormat.LdpVc
): Promise<boolean> => {

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
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
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'
Expand All @@ -21,6 +21,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)
Expand Down Expand Up @@ -195,9 +196,9 @@ 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'),
isValid: await validateStatus(credential.credentialStatus, agentContext, ClaimFormat.JwtVc)
}
}

Expand Down Expand Up @@ -376,6 +377,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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -13,9 +12,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<string | JsonObject>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { W3cCredentialStatusOptions } from './w3c-credential-status/W3cCredentialStatus'
import type { JsonObject } from '../../../../types'
import type { SingleOrArray } from '../../../../utils'

Expand All @@ -9,5 +10,6 @@ export interface W3cJsonCredential {
issuanceDate: string
expirationDate?: string
credentialSubject: SingleOrArray<JsonObject>
credentialStatus?: SingleOrArray<W3cCredentialStatusOptions>
[key: string]: unknown
}
Original file line number Diff line number Diff line change
@@ -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
}

Original file line number Diff line number Diff line change
@@ -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<string, unknown>) {
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(', ')}`
},
},
})
}
}
Loading
Loading