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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,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 @@ -71,6 +73,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 @@ -6,7 +6,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 { createKmsKeyPairClass } from '../../../crypto/KmsKeyPair'
Expand All @@ -22,6 +22,7 @@ import { SingleOrArray } from '../../../types'
import { PublicJwk } from '../../kms'
import { SignatureSuiteRegistry } from './SignatureSuiteRegistry'
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 @@ -115,10 +116,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 @@ -265,12 +265,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
Expand Up @@ -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
Expand Down Expand Up @@ -265,6 +285,52 @@ 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,26 @@ 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'],
Expand Down Expand Up @@ -83,6 +103,63 @@ 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'],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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'

// 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
}
13 changes: 10 additions & 3 deletions packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ 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 { CredoError } from '../../../error'
Expand All @@ -21,6 +26,7 @@ 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'
Expand Down Expand Up @@ -197,9 +203,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 @@ -375,6 +381,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, SingleOrArray } from '../../../../types'
import type { W3cCredentialSubjectOptions } from './W3cCredentialSubject'
import type { W3cIssuerOptions } from './W3cIssuer'

import { Expose, Type } from 'class-transformer'
Expand All @@ -12,9 +11,14 @@ 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,5 +1,7 @@
import type { JsonObject, SingleOrArray } from '../../../../types'

import type { W3cCredentialStatusOptions } from './w3c-credential-status/W3cCredentialStatus'

export interface W3cJsonCredential {
'@context': Array<string | JsonObject>
id?: string
Expand All @@ -8,5 +10,6 @@ export interface W3cJsonCredential {
issuanceDate: string
expirationDate?: string
credentialSubject: SingleOrArray<JsonObject>
credentialStatus?: SingleOrArray<W3cCredentialStatusOptions>
[key: string]: unknown
}
1 change: 1 addition & 0 deletions packages/core/src/modules/vc/models/credential/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './W3cCredentialSubject'
export * from './W3cIssuer'
export * from './W3cCredential'
export * from './W3cVerifiableCredential'
export * from './w3c-credential-status'
Loading
Loading