Skip to content

Commit d945ca4

Browse files
committed
fix: improve dcql support
1 parent d2d3f7a commit d945ca4

10 files changed

+105
-163
lines changed

packages/core/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
},
2727
"dependencies": {
2828
"@digitalcredentials/jsonld": "^6.0.0",
29-
"dcql": "^0.2.11",
29+
"dcql": "^0.2.13",
3030
"@digitalcredentials/jsonld-signatures": "^9.4.0",
3131
"@digitalcredentials/vc": "^6.0.1",
3232
"@multiformats/base-x": "^4.0.1",

packages/core/src/modules/dcql/DcqlService.ts

+52-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { AgentContext } from '../../agent'
22

3-
import { DcqlCredentialRepresentation, DcqlMdocRepresentation, DcqlQuery, DcqlSdJwtVcRepresentation } from 'dcql'
3+
import { DcqlCredential, DcqlMdocCredential, DcqlQuery, DcqlSdJwtVcCredential } from 'dcql'
44
import { injectable } from 'tsyringe'
55

66
import { JsonValue } from '../../types'
@@ -34,7 +34,7 @@ export class DcqlService {
3434
*/
3535
private async queryCredentialsForDcqlQuery(
3636
agentContext: AgentContext,
37-
dcqlQuery: DcqlQuery
37+
dcqlQuery: DcqlQuery.Input
3838
): Promise<Array<SdJwtVcRecord | W3cCredentialRecord | MdocRecord>> {
3939
const w3cCredentialRepository = agentContext.dependencyManager.resolve(W3cCredentialRepository)
4040

@@ -47,47 +47,83 @@ export class DcqlService {
4747

4848
const allRecords: Array<SdJwtVcRecord | W3cCredentialRecord | MdocRecord> = []
4949

50-
// query the wallet ourselves first to avoid the need to query the pex library for all
51-
// credentials for every proof request
5250
const w3cCredentialRecords =
5351
formats.has('jwt_vc_json') || formats.has('jwt_vc_json-ld')
5452
? await w3cCredentialRepository.getAll(agentContext)
5553
: []
5654
allRecords.push(...w3cCredentialRecords)
5755

58-
const sdJwtVcApi = this.getSdJwtVcApi(agentContext)
59-
const sdJwtVcRecords = formats.has('vc+sd-jwt') ? await sdJwtVcApi.getAll() : []
60-
allRecords.push(...sdJwtVcRecords)
56+
// query the wallet ourselves first to avoid the need to query the pex library for all
57+
// credentials for every proof request
58+
const mdocDoctypes = dcqlQuery.credentials
59+
.filter((credentialQuery) => credentialQuery.format === 'mso_mdoc')
60+
.map((c) => c.meta?.doctype_value)
61+
const allMdocCredentialQueriesSpecifyDoctype = mdocDoctypes.every((doctype) => doctype)
6162

6263
const mdocApi = this.getMdocApi(agentContext)
63-
const mdocRecords = formats.has('mso_mdoc') ? await mdocApi.getAll() : []
64-
allRecords.push(...mdocRecords)
64+
if (allMdocCredentialQueriesSpecifyDoctype) {
65+
const mdocRecords = await mdocApi.findAllByQuery({
66+
$or: mdocDoctypes.map((docType) => ({
67+
docType: docType as string,
68+
})),
69+
})
70+
allRecords.push(...mdocRecords)
71+
} else {
72+
const mdocRecords = await mdocApi.getAll()
73+
allRecords.push(...mdocRecords)
74+
}
75+
76+
// query the wallet ourselves first to avoid the need to query the pex library for all
77+
// credentials for every proof request
78+
const sdJwtVctValues = dcqlQuery.credentials
79+
.filter((credentialQuery) => credentialQuery.format === 'vc+sd-jwt')
80+
.flatMap((c) => c.meta?.vct_values)
81+
82+
const allSdJwtVcQueriesSpecifyDoctype = sdJwtVctValues.every((vct) => vct)
83+
84+
const sdJwtVcApi = this.getSdJwtVcApi(agentContext)
85+
if (allSdJwtVcQueriesSpecifyDoctype) {
86+
const sdjwtVcRecords = await sdJwtVcApi.findAllByQuery({
87+
$or: sdJwtVctValues.map((vct) => ({
88+
vct: vct as string,
89+
})),
90+
})
91+
allRecords.push(...sdjwtVcRecords)
92+
} else {
93+
const sdJwtVcRecords = await sdJwtVcApi.getAll()
94+
allRecords.push(...sdJwtVcRecords)
95+
}
6596

6697
return allRecords
6798
}
6899

69-
public async getCredentialsForRequest(agentContext: AgentContext, dcqlQuery: DcqlQuery): Promise<DcqlQueryResult> {
100+
public async getCredentialsForRequest(
101+
agentContext: AgentContext,
102+
dcqlQuery: DcqlQuery.Input
103+
): Promise<DcqlQueryResult> {
70104
const credentialRecords = await this.queryCredentialsForDcqlQuery(agentContext, dcqlQuery)
71105

72-
const mappedCredentials: DcqlCredentialRepresentation[] = credentialRecords.map((record) => {
106+
const dcqlCredentials: DcqlCredential[] = credentialRecords.map((record) => {
73107
if (record.type === 'MdocRecord') {
74108
return {
75-
docType: record.getTags().docType,
109+
credentialFormat: 'mso_mdoc',
110+
doctype: record.getTags().docType,
76111
namespaces: Mdoc.fromBase64Url(record.base64Url).issuerSignedNamespaces,
77-
} satisfies DcqlMdocRepresentation
112+
} satisfies DcqlMdocCredential
78113
} else if (record.type === 'SdJwtVcRecord') {
79114
return {
115+
credentialFormat: 'vc+sd-jwt',
80116
vct: record.getTags().vct,
81117
claims: this.getSdJwtVcApi(agentContext).fromCompact(record.compactSdJwtVc)
82-
.prettyClaims as DcqlSdJwtVcRepresentation.Claims,
83-
} satisfies DcqlSdJwtVcRepresentation
118+
.prettyClaims as DcqlSdJwtVcCredential.Claims,
119+
} satisfies DcqlSdJwtVcCredential
84120
} else {
85121
// TODO:
86122
throw new DcqlError('W3C credentials are not supported yet')
87123
}
88124
})
89125

90-
const queryResult = DcqlQuery.query(dcqlQuery, mappedCredentials)
126+
const queryResult = DcqlQuery.query(DcqlQuery.parse(dcqlQuery), dcqlCredentials)
91127
const matchesWithRecord = Object.fromEntries(
92128
Object.entries(queryResult.credential_matches).map(([credential_query_id, result]) => {
93129
return [credential_query_id, { ...result, record: credentialRecords[result.credential_index] }]

packages/core/src/modules/dcql/models/index.ts

+19-13
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,31 @@ import type { MdocRecord } from '../../mdoc'
44
import type { SdJwtVcRecord } from '../../sd-jwt-vc'
55
import type { W3cCredentialRecord } from '../../vc'
66
import type {
7-
DcqlQueryResult as InternalDcqlQueryResult,
8-
DcqlPresentationRecord as _DcqlPresentationRecord,
7+
DcqlQueryResult as _DcqlQueryResult,
8+
DcqlQuery as _DcqlQuery,
9+
DcqlCredential as _DcqlCredential,
10+
DcqlMdocCredential as _DcqlMdocCredential,
11+
DcqlW3cVcCredential as _DcqlW3cVcCredential,
12+
DcqlSdJwtVcCredential as _DcqlSdJwtVcCredential,
13+
DcqlPresentation as _DcqlPresentation,
14+
DcqlPresentationResult as _DcqlPresentationResult,
915
} from 'dcql'
1016

11-
export type {
12-
DcqlQuery,
13-
DcqlCredentialRepresentation,
14-
DcqlMdocRepresentation,
15-
DcqlSdJwtVcRepresentation,
16-
DcqlPresentationQuery as DcqlPresentationQueryResult,
17-
} from 'dcql'
17+
export type DcqlQuery = _DcqlQuery.Input
18+
export type DcqlCredential = _DcqlCredential.Model['Input']
19+
export type DcqlMdocCredential = _DcqlMdocCredential.Model['Input']
20+
export type DcqlSdJwtVcCredential = _DcqlSdJwtVcCredential.Model['Input']
21+
export type DcqlW3cVcCredential = _DcqlW3cVcCredential.Model['Input']
1822

19-
export type DcqlMatchWithRecord = InternalDcqlQueryResult.CredentialMatch & {
23+
export type DcqlMatchWithRecord = {
2024
record: W3cCredentialRecord | SdJwtVcRecord | MdocRecord
21-
}
25+
} & _DcqlQueryResult['credential_matches'][number]
2226

23-
export type DcqlQueryResult = Omit<InternalDcqlQueryResult, 'credential_matches'> & {
27+
export type DcqlQueryResult = Omit<_DcqlQueryResult.Input, 'credential_matches'> & {
2428
credential_matches: Record<string, DcqlMatchWithRecord>
2529
}
2630

27-
export type DcqlEncodedPresentations = _DcqlPresentationRecord
31+
export type DcqlEncodedPresentations = _DcqlPresentation.Input
2832
export type DcqlPresentation = Record<string, VerifiablePresentation>
33+
34+
export type DcqlPresentationResult = _DcqlPresentationResult.Input

packages/core/src/modules/dcql/utils/DcqlPresentationsToCreate.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { SdJwtVcRecord } from '../../sd-jwt-vc'
22
import type { DcqlCredentialsForRequest } from '../models'
3-
import type { DcqlSdJwtVcRepresentation, DcqlW3cVcRepresentation, DcqlMdocRepresentation } from 'dcql'
3+
import type { DcqlSdJwtVcCredential, DcqlMdocCredential, DcqlW3cVcCredential } from 'dcql'
44

55
import { MdocRecord } from '../../mdoc'
66
import { W3cCredentialRecord, ClaimFormat } from '../../vc'
@@ -10,14 +10,14 @@ export interface DcqlSdJwtVcPresentationToCreate {
1010
claimFormat: ClaimFormat.SdJwtVc
1111
subjectIds: [] // subject is included in the cnf of the sd-jwt and automatically extracted by PEX
1212
credentialRecord: SdJwtVcRecord
13-
disclosedPayload: DcqlSdJwtVcRepresentation.Claims
13+
disclosedPayload: DcqlSdJwtVcCredential.Claims
1414
}
1515

1616
export interface DcqlJwtVpPresentationToCreate {
1717
claimFormat: ClaimFormat.JwtVp
1818
subjectIds: [string] // only one subject id supported for JWT VP
1919
credentialRecord: W3cCredentialRecord
20-
disclosedPayload: DcqlW3cVcRepresentation.Claims
20+
disclosedPayload: DcqlW3cVcCredential.Claims
2121
}
2222

2323
export interface DcqlLdpVpPresentationToCreate {
@@ -26,14 +26,14 @@ export interface DcqlLdpVpPresentationToCreate {
2626
// support yet for adding multiple proofs to an LDP-VP
2727
subjectIds: undefined | [string]
2828
credentialRecord: W3cCredentialRecord
29-
disclosedPayload: DcqlW3cVcRepresentation.Claims
29+
disclosedPayload: DcqlW3cVcCredential.Claims
3030
}
3131

3232
export interface DcqlMdocPresentationToCreate {
3333
claimFormat: ClaimFormat.MsoMdoc
3434
subjectIds: []
3535
credentialRecord: MdocRecord
36-
disclosedPayload: DcqlMdocRepresentation.NameSpaces
36+
disclosedPayload: DcqlMdocCredential.NameSpaces
3737
}
3838

3939
export type DcqlPresentationToCreate = Record<
@@ -55,21 +55,21 @@ export function dcqlGetPresentationsToCreate(
5555
match.credentialRecord.credential.claimFormat === ClaimFormat.JwtVc ? ClaimFormat.JwtVp : ClaimFormat.LdpVp,
5656
subjectIds: [match.credentialRecord.credential.credentialSubjectIds[0]],
5757
credentialRecord: match.credentialRecord,
58-
disclosedPayload: match.disclosedPayload as DcqlW3cVcRepresentation.Claims,
58+
disclosedPayload: match.disclosedPayload as DcqlW3cVcCredential.Claims,
5959
}
6060
} else if (match.credentialRecord instanceof MdocRecord) {
6161
presentationsToCreate[credentialQueryId] = {
6262
claimFormat: ClaimFormat.MsoMdoc,
6363
subjectIds: [],
6464
credentialRecord: match.credentialRecord,
65-
disclosedPayload: match.disclosedPayload as DcqlMdocRepresentation.NameSpaces,
65+
disclosedPayload: match.disclosedPayload as DcqlMdocCredential.NameSpaces,
6666
}
6767
} else {
6868
presentationsToCreate[credentialQueryId] = {
6969
claimFormat: ClaimFormat.SdJwtVc,
7070
subjectIds: [],
7171
credentialRecord: match.credentialRecord,
72-
disclosedPayload: match.disclosedPayload as DcqlW3cVcRepresentation.Claims,
72+
disclosedPayload: match.disclosedPayload as DcqlW3cVcCredential.Claims,
7373
}
7474
}
7575
}

packages/openid4vc/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
},
2828
"dependencies": {
2929
"@credo-ts/core": "workspace:*",
30-
"@sphereon/did-auth-siop": "https://gitpkg.vercel.app/animo/OID4VC/packages/siop-oid4vp?funke",
30+
"@sphereon/did-auth-siop": "link:/../../../CODE/OID4VC/packages/siop-oid4vp",
3131
"@sphereon/oid4vc-common": "0.16.1-fix.173",
3232
"@sphereon/ssi-types": "0.30.2-next.135",
3333
"class-transformer": "^0.5.1",

packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export class OpenId4VcSiopHolderService {
138138
})
139139

140140
dcqlOptions = {
141-
encodedPresentations: await this.dcqlService.getEncodedPresentations(dcqlPresentation),
141+
dcqlPresentation: await this.dcqlService.getEncodedPresentations(dcqlPresentation),
142142
}
143143

144144
if (wantsIdToken && !openIdTokenIssuer) {

packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ import {
5959
RevocationVerification,
6060
RP,
6161
SupportedVersion,
62-
assertValidDcqlPresentationRecord,
62+
assertValidDcqlPresentationResult,
6363
} from '@sphereon/did-auth-siop'
6464
import {
65-
extractPresentationRecordFromDcqlVpToken,
65+
extractDcqlPresentationFromDcqlVpToken,
6666
extractPresentationsFromVpToken,
6767
} from '@sphereon/did-auth-siop/dist/authorization-response/OpenID4VP'
6868
import { filter, first, firstValueFrom, map, timeout } from 'rxjs'
@@ -375,20 +375,20 @@ export class OpenId4VcSiopVerifierService {
375375
if (dcqlQuery) {
376376
const dcqlQueryVpToken = authorizationResponse.payload.vp_token
377377
const dcqlPresentation = Object.fromEntries(
378-
Object.entries(
379-
extractPresentationRecordFromDcqlVpToken(dcqlQueryVpToken as string, { hasher: Hasher.hash })
380-
).map(([key, value]) => {
381-
return [key, getVerifiablePresentationFromSphereonWrapped(value)]
382-
})
378+
Object.entries(extractDcqlPresentationFromDcqlVpToken(dcqlQueryVpToken as string, { hasher: Hasher.hash })).map(
379+
([key, value]) => {
380+
return [key, getVerifiablePresentationFromSphereonWrapped(value)]
381+
}
382+
)
383383
)
384384

385-
const presentationQueryResult = await assertValidDcqlPresentationRecord(
385+
const presentationQueryResult = await assertValidDcqlPresentationResult(
386386
authorizationResponse.payload.vp_token as string,
387387
dcqlQuery,
388388
{ hasher: Hasher.hash }
389389
)
390390

391-
dcql = { presentation: dcqlPresentation, presentationQueryResult }
391+
dcql = { presentation: dcqlPresentation, presentationResult: presentationQueryResult }
392392
}
393393

394394
if (!idToken && !(presentationExchange || dcqlQuery)) {

packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierServiceOptions.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type {
1313
DcqlQuery,
1414
DcqlPresentation,
1515
DifPexPresentationWithDescriptor,
16-
DcqlPresentationQueryResult,
16+
DcqlPresentationResult,
1717
} from '@credo-ts/core'
1818

1919
export type ResponseMode = 'direct_post' | 'direct_post.jwt'
@@ -81,7 +81,7 @@ export interface OpenId4VcSiopVerifiedAuthorizationResponsePresentationExchange
8181

8282
export interface OpenId4VcSiopVerifiedAuthorizationResponseDcql {
8383
presentation: DcqlPresentation
84-
presentationQueryResult: DcqlPresentationQueryResult
84+
presentationResult: DcqlPresentationResult
8585
}
8686

8787
/**

packages/openid4vc/tests/openid4vc.e2e.test.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -2330,16 +2330,16 @@ describe('OpenId4Vc', () => {
23302330
typed: true,
23312331
success: true,
23322332
output: {
2333-
docType: 'org.eu.university',
2333+
doctype: 'org.eu.university',
2334+
credentialFormat: 'mso_mdoc',
23342335
namespaces: {
23352336
'eu.europa.ec.eudi.pid.1': {
23362337
name: 'John Doe',
23372338
degree: 'bachelor',
23382339
},
23392340
},
23402341
},
2341-
issues: undefined,
2342-
credential_index: 1,
2342+
credential_index: 0,
23432343
claim_set_index: undefined,
23442344
all: expect.any(Array),
23452345
record: expect.any(MdocRecord),
@@ -2348,13 +2348,13 @@ describe('OpenId4Vc', () => {
23482348
typed: true,
23492349
success: true,
23502350
output: {
2351+
credentialFormat: 'vc+sd-jwt',
23512352
vct: 'OpenBadgeCredential',
23522353
claims: {
23532354
university: 'innsbruck',
23542355
},
23552356
},
2356-
issues: undefined,
2357-
credential_index: 0,
2357+
credential_index: 1,
23582358
claim_set_index: undefined,
23592359
all: expect.any(Array),
23602360
record: expect.any(SdJwtVcRecord),

0 commit comments

Comments
 (0)