@sphereon/ssi-sdk.siopv2-oid4vp-op-auth 0.34.1-next.29 → 0.34.1-next.299

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,31 +1,31 @@
1
- import { AuthorizationRequest, SupportedVersion } from '@sphereon/did-auth-siop'
2
- import { IPresentationDefinition, PEX } from '@sphereon/pex'
3
- import { InputDescriptorV1, InputDescriptorV2, PresentationDefinitionV1, PresentationDefinitionV2 } from '@sphereon/pex-models'
1
+ import { AuthorizationRequest } from '@sphereon/did-auth-siop'
2
+ import type { PartialSdJwtDecodedVerifiableCredential, PartialSdJwtKbJwt } from '@sphereon/pex/dist/main/lib/index.js'
3
+ import { calculateSdHash } from '@sphereon/pex/dist/main/lib/utils/index.js'
4
+ import { getOrCreatePrimaryIdentifier, SupportedDidMethodEnum } from '@sphereon/ssi-sdk-ext.did-utils'
4
5
  import { isOID4VCIssuerIdentifier, ManagedIdentifierOptsOrResult } from '@sphereon/ssi-sdk-ext.identifier-resolution'
6
+ import { encodeJoseBlob } from '@sphereon/ssi-sdk.core'
5
7
  import { UniqueDigitalCredential, verifiableCredentialForRoleFilter } from '@sphereon/ssi-sdk.credential-store'
6
- import { ConnectionType, CredentialRole } from '@sphereon/ssi-sdk.data-store'
7
- import { CredentialMapper, HasherSync, Loggers, OriginalVerifiableCredential, PresentationSubmission } from '@sphereon/ssi-types'
8
- import { OID4VP, OpSession } from '../session'
8
+ import { ConnectionType } from '@sphereon/ssi-sdk.data-store-types'
9
+ import { defaultGenerateDigest } from '@sphereon/ssi-sdk.sd-jwt'
9
10
  import {
10
- DidAgents,
11
- LOGGER_NAMESPACE,
12
- RequiredContext,
13
- SelectableCredential,
14
- SelectableCredentialsMap,
15
- Siopv2HolderEvent,
16
- SuitableCredentialAgents,
17
- VerifiableCredentialsWithDefinition,
18
- VerifiablePresentationWithDefinition,
19
- } from '../types'
11
+ CredentialMapper,
12
+ CredentialRole,
13
+ HasherSync,
14
+ Loggers,
15
+ OriginalVerifiableCredential,
16
+ SdJwtDecodedVerifiableCredential,
17
+ } from '@sphereon/ssi-types'
20
18
  import { IAgentContext, IDIDManager } from '@veramo/core'
21
- import { getOrCreatePrimaryIdentifier, SupportedDidMethodEnum } from '@sphereon/ssi-sdk-ext.did-utils'
22
- import { defaultHasher, encodeJoseBlob } from '@sphereon/ssi-sdk.core'
23
- import { DcqlCredential, DcqlCredentialPresentation, DcqlPresentation, DcqlQuery } from 'dcql'
19
+ import { DcqlPresentation, DcqlQuery } from 'dcql'
20
+ import { OpSession } from '../session'
21
+ import { LOGGER_NAMESPACE, RequiredContext, SelectableCredential, SelectableCredentialsMap, Siopv2HolderEvent } from '../types'
24
22
  import { convertToDcqlCredentials } from '../utils/dcql'
25
- import { getOriginalVerifiableCredential } from '../utils/CredentialUtils'
23
+
24
+ const CLOCK_SKEW = 120
26
25
 
27
26
  export const logger = Loggers.DEFAULT.get(LOGGER_NAMESPACE)
28
27
 
28
+ // @ts-ignore
29
29
  const createEbsiIdentifier = async (agentContext: IAgentContext<IDIDManager>): Promise<ManagedIdentifierOptsOrResult> => {
30
30
  logger.log(`No EBSI key present yet. Creating a new one...`)
31
31
  const { result: newIdentifier, created } = await getOrCreatePrimaryIdentifier(agentContext, {
@@ -39,9 +39,10 @@ const createEbsiIdentifier = async (agentContext: IAgentContext<IDIDManager>): P
39
39
  return await agentContext.agent.identifierManagedGetByDid({ identifier: newIdentifier.did })
40
40
  }
41
41
 
42
+ // @ts-ignore
42
43
  const hasEbsiClient = async (authorizationRequest: AuthorizationRequest) => {
43
- const clientId = await authorizationRequest.getMergedProperty<string>('client_id')
44
- const redirectUri = await authorizationRequest.getMergedProperty<string>('redirect_uri')
44
+ const clientId = authorizationRequest.getMergedProperty<string>('client_id')
45
+ const redirectUri = authorizationRequest.getMergedProperty<string>('redirect_uri')
45
46
  return clientId?.toLowerCase().includes('.ebsi.eu') || redirectUri?.toLowerCase().includes('.ebsi.eu')
46
47
  }
47
48
 
@@ -49,293 +50,186 @@ export const siopSendAuthorizationResponse = async (
49
50
  connectionType: ConnectionType,
50
51
  args: {
51
52
  sessionId: string
52
- verifiableCredentialsWithDefinition?: VerifiableCredentialsWithDefinition[]
53
+ credentials: Array<UniqueDigitalCredential | OriginalVerifiableCredential>
53
54
  idOpts?: ManagedIdentifierOptsOrResult
54
55
  isFirstParty?: boolean
55
56
  hasher?: HasherSync
56
- dcqlQuery?: DcqlQuery
57
57
  },
58
58
  context: RequiredContext,
59
59
  ) => {
60
60
  const { agent } = context
61
- const agentContext = { ...context, agent: context.agent as DidAgents }
62
- let { idOpts, isFirstParty, hasher = defaultHasher } = args
63
-
61
+ const { credentials } = args
64
62
  if (connectionType !== ConnectionType.SIOPv2_OpenID4VP) {
65
63
  return Promise.reject(Error(`No supported authentication provider for type: ${connectionType}`))
66
64
  }
65
+
67
66
  const session: OpSession = await agent.siopGetOPSession({ sessionId: args.sessionId })
68
67
  const request = await session.getAuthorizationRequest()
69
- const aud = await request.authorizationRequest.getMergedProperty<string>('aud')
68
+ const aud = request.authorizationRequest.getMergedProperty<string>('aud')
70
69
  logger.debug(`AUD: ${aud}`)
71
70
  logger.debug(JSON.stringify(request.authorizationRequest))
72
71
 
73
- let presentationsAndDefs: VerifiablePresentationWithDefinition[] | undefined
74
- let presentationSubmission: PresentationSubmission | undefined
75
- if (await session.hasPresentationDefinitions()) {
76
- const oid4vp: OID4VP = await session.getOID4VP({ hasher })
77
-
78
- const credentialsAndDefinitions = args.verifiableCredentialsWithDefinition
79
- ? args.verifiableCredentialsWithDefinition
80
- : await oid4vp.filterCredentialsAgainstAllDefinitions(CredentialRole.HOLDER)
81
- const domain =
82
- ((await request.authorizationRequest.getMergedProperty('client_id')) as string) ??
83
- request.issuer ??
84
- (request.versions.includes(SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1)
85
- ? 'https://self-issued.me/v2/openid-vc'
86
- : 'https://self-issued.me/v2')
87
- logger.log(`NONCE: ${session.nonce}, domain: ${domain}`)
88
-
89
- const firstUniqueDC = credentialsAndDefinitions[0].credentials[0]
90
- if (typeof firstUniqueDC !== 'object' || !('digitalCredential' in firstUniqueDC)) {
91
- return Promise.reject(Error('SiopMachine only supports UniqueDigitalCredentials for now'))
92
- }
72
+ const domain = ((await request.authorizationRequest.getMergedProperty('client_id')) as string) ?? request.issuer ?? 'https://self-issued.me/v2'
93
73
 
94
- let identifier: ManagedIdentifierOptsOrResult
95
- const digitalCredential = firstUniqueDC.digitalCredential
96
- const firstVC = firstUniqueDC.uniformVerifiableCredential
97
- const holder = CredentialMapper.isSdJwtDecodedCredential(firstVC)
98
- ? firstVC.decodedPayload.cnf?.jwk
99
- ? //TODO SDK-19: convert the JWK to hex and search for the appropriate key and associated DID
100
- //doesn't apply to did:jwk only, as you can represent any DID key as a JWK. So whenever you encounter a JWK it doesn't mean it had to come from a did:jwk in the system. It just can always be represented as a did:jwk
101
- `did:jwk:${encodeJoseBlob(firstVC.decodedPayload.cnf?.jwk)}#0`
102
- : firstVC.decodedPayload.sub
103
- : Array.isArray(firstVC.credentialSubject)
104
- ? firstVC.credentialSubject[0].id
105
- : firstVC.credentialSubject.id
106
- if (!digitalCredential.kmsKeyRef) {
107
- // In case the store does not have the kmsKeyRef lets search for the holder
108
-
109
- if (!holder) {
110
- return Promise.reject(`No holder found and no kmsKeyRef in DB. Cannot determine identifier to use`)
111
- }
112
- try {
113
- identifier = await session.context.agent.identifierManagedGet({ identifier: holder })
114
- } catch (e) {
115
- logger.debug(`Holder DID not found: ${holder}`)
116
- throw e
117
- }
118
- } else if (isOID4VCIssuerIdentifier(digitalCredential.kmsKeyRef)) {
119
- identifier = await session.context.agent.identifierManagedGetByOID4VCIssuer({
120
- identifier: firstUniqueDC.digitalCredential.kmsKeyRef,
121
- })
122
- } else {
123
- switch (digitalCredential.subjectCorrelationType) {
124
- case 'DID':
125
- identifier = await session.context.agent.identifierManagedGetByDid({
126
- identifier: digitalCredential.subjectCorrelationId ?? holder,
127
- kmsKeyRef: digitalCredential.kmsKeyRef,
128
- })
129
- break
130
- // TODO other implementations?
131
- default:
132
- if (digitalCredential.subjectCorrelationId?.startsWith('did:') || holder?.startsWith('did:')) {
133
- identifier = await session.context.agent.identifierManagedGetByDid({
134
- identifier: digitalCredential.subjectCorrelationId ?? holder,
135
- kmsKeyRef: digitalCredential.kmsKeyRef,
136
- })
137
- } else {
138
- // Since we are using the kmsKeyRef we will find the KID regardless of the identifier. We set it for later access though
139
- identifier = await session.context.agent.identifierManagedGetByKid({
140
- identifier: digitalCredential.subjectCorrelationId ?? holder ?? digitalCredential.kmsKeyRef,
141
- kmsKeyRef: digitalCredential.kmsKeyRef,
142
- })
143
- }
144
- }
145
- }
146
-
147
- if (identifier === undefined && idOpts !== undefined && (await hasEbsiClient(request.authorizationRequest))) {
148
- identifier = await createEbsiIdentifier(agentContext)
149
- }
150
- logger.debug(`Identifier`, identifier)
74
+ logger.debug(`NONCE: ${session.nonce}, domain: ${domain}`)
151
75
 
152
- // TODO Add mdoc support
76
+ const firstUniqueDC = credentials[0]
77
+ if (typeof firstUniqueDC !== 'object' || !('digitalCredential' in firstUniqueDC)) {
78
+ return Promise.reject(Error('SiopMachine only supports UniqueDigitalCredentials for now'))
79
+ }
153
80
 
154
- presentationsAndDefs = await oid4vp.createVerifiablePresentations(CredentialRole.HOLDER, credentialsAndDefinitions, {
155
- idOpts: identifier,
156
- proofOpts: {
157
- nonce: session.nonce,
158
- domain,
159
- },
81
+ let identifier: ManagedIdentifierOptsOrResult
82
+ const digitalCredential = firstUniqueDC.digitalCredential
83
+ const firstVC = firstUniqueDC.uniformVerifiableCredential
84
+ const holder = CredentialMapper.isSdJwtDecodedCredential(firstVC)
85
+ ? firstVC.decodedPayload.cnf?.jwk
86
+ ? //TODO SDK-19: convert the JWK to hex and search for the appropriate key and associated DID
87
+ //doesn't apply to did:jwk only, as you can represent any DID key as a JWK. So whenever you encounter a JWK it doesn't mean it had to come from a did:jwk in the system. It just can always be represented as a did:jwk
88
+ `did:jwk:${encodeJoseBlob(firstVC.decodedPayload.cnf?.jwk)}#0`
89
+ : firstVC.decodedPayload.sub
90
+ : Array.isArray(firstVC.credentialSubject)
91
+ ? firstVC.credentialSubject[0].id
92
+ : firstVC.credentialSubject.id
93
+ if (!digitalCredential.kmsKeyRef) {
94
+ // In case the store does not have the kmsKeyRef lets search for the holder
95
+
96
+ if (!holder) {
97
+ return Promise.reject(`No holder found and no kmsKeyRef in DB. Cannot determine identifier to use`)
98
+ }
99
+ try {
100
+ identifier = await session.context.agent.identifierManagedGet({ identifier: holder })
101
+ } catch (e) {
102
+ logger.debug(`Holder DID not found: ${holder}`)
103
+ throw e
104
+ }
105
+ } else if (isOID4VCIssuerIdentifier(digitalCredential.kmsKeyRef)) {
106
+ identifier = await session.context.agent.identifierManagedGetByOID4VCIssuer({
107
+ identifier: firstUniqueDC.digitalCredential.kmsKeyRef,
160
108
  })
161
- if (!presentationsAndDefs || presentationsAndDefs.length === 0) {
162
- throw Error('No verifiable presentations could be created')
163
- } else if (presentationsAndDefs.length > 1) {
164
- throw Error(`Only one verifiable presentation supported for now. Got ${presentationsAndDefs.length}`)
109
+ } else {
110
+ switch (digitalCredential.subjectCorrelationType) {
111
+ case 'DID':
112
+ identifier = await session.context.agent.identifierManagedGetByDid({
113
+ identifier: digitalCredential.subjectCorrelationId ?? holder,
114
+ kmsKeyRef: digitalCredential.kmsKeyRef,
115
+ })
116
+ break
117
+ // TODO other implementations?
118
+ default:
119
+ // Since we are using the kmsKeyRef we will find the KID regardless of the identifier. We set it for later access though
120
+ identifier = await session.context.agent.identifierManagedGetByKid({
121
+ identifier: digitalCredential.subjectCorrelationId ?? holder ?? digitalCredential.kmsKeyRef,
122
+ kmsKeyRef: digitalCredential.kmsKeyRef,
123
+ })
165
124
  }
125
+ }
166
126
 
167
- idOpts = presentationsAndDefs[0].idOpts
168
- presentationSubmission = presentationsAndDefs[0].presentationSubmission
169
-
170
- logger.log(`Definitions and locations:`, JSON.stringify(presentationsAndDefs?.[0]?.verifiablePresentations, null, 2))
171
- logger.log(`Presentation Submission:`, JSON.stringify(presentationSubmission, null, 2))
172
- const mergedVerifiablePresentations = presentationsAndDefs?.flatMap((pd) => pd.verifiablePresentations) || []
173
- return await session.sendAuthorizationResponse({
174
- ...(presentationsAndDefs && { verifiablePresentations: mergedVerifiablePresentations }),
175
- ...(presentationSubmission && { presentationSubmission }),
176
- // todo: Change issuer value in case we do not use identifier. Use key.meta.jwkThumbprint then
177
- responseSignerOpts: idOpts!,
178
- isFirstParty,
179
- })
180
- } else if (request.dcqlQuery) {
181
- if (args.verifiableCredentialsWithDefinition !== undefined && args.verifiableCredentialsWithDefinition !== null) {
182
- const vcs = args.verifiableCredentialsWithDefinition.flatMap((vcd) => vcd.credentials)
183
- const domain =
184
- ((await request.authorizationRequest.getMergedProperty('client_id')) as string) ??
185
- request.issuer ??
186
- (request.versions.includes(SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1)
187
- ? 'https://self-issued.me/v2/openid-vc'
188
- : 'https://self-issued.me/v2')
189
- logger.debug(`NONCE: ${session.nonce}, domain: ${domain}`)
190
-
191
- const firstUniqueDC = vcs[0]
192
- if (typeof firstUniqueDC !== 'object' || !('digitalCredential' in firstUniqueDC)) {
193
- return Promise.reject(Error('SiopMachine only supports UniqueDigitalCredentials for now'))
194
- }
127
+ const dcqlCredentialsWithCredentials = new Map(credentials.map((vc) => [convertToDcqlCredentials(vc), vc]))
195
128
 
196
- let identifier: ManagedIdentifierOptsOrResult
197
- const digitalCredential = firstUniqueDC.digitalCredential
198
- const firstVC = firstUniqueDC.uniformVerifiableCredential
199
- const holder = CredentialMapper.isSdJwtDecodedCredential(firstVC)
200
- ? firstVC.decodedPayload.cnf?.jwk
201
- ? //TODO SDK-19: convert the JWK to hex and search for the appropriate key and associated DID
202
- //doesn't apply to did:jwk only, as you can represent any DID key as a JWK. So whenever you encounter a JWK it doesn't mean it had to come from a did:jwk in the system. It just can always be represented as a did:jwk
203
- `did:jwk:${encodeJoseBlob(firstVC.decodedPayload.cnf?.jwk)}#0`
204
- : firstVC.decodedPayload.sub
205
- : Array.isArray(firstVC.credentialSubject)
206
- ? firstVC.credentialSubject[0].id
207
- : firstVC.credentialSubject.id
208
- if (!digitalCredential.kmsKeyRef) {
209
- // In case the store does not have the kmsKeyRef lets search for the holder
210
-
211
- if (!holder) {
212
- return Promise.reject(`No holder found and no kmsKeyRef in DB. Cannot determine identifier to use`)
213
- }
214
- try {
215
- identifier = await session.context.agent.identifierManagedGet({ identifier: holder })
216
- } catch (e) {
217
- logger.debug(`Holder DID not found: ${holder}`)
218
- throw e
219
- }
220
- } else if (isOID4VCIssuerIdentifier(digitalCredential.kmsKeyRef)) {
221
- identifier = await session.context.agent.identifierManagedGetByOID4VCIssuer({
222
- identifier: firstUniqueDC.digitalCredential.kmsKeyRef,
223
- })
224
- } else {
225
- switch (digitalCredential.subjectCorrelationType) {
226
- case 'DID':
227
- identifier = await session.context.agent.identifierManagedGetByDid({
228
- identifier: digitalCredential.subjectCorrelationId ?? holder,
229
- kmsKeyRef: digitalCredential.kmsKeyRef,
230
- })
231
- break
232
- // TODO other implementations?
233
- default:
234
- // Since we are using the kmsKeyRef we will find the KID regardless of the identifier. We set it for later access though
235
- identifier = await session.context.agent.identifierManagedGetByKid({
236
- identifier: digitalCredential.subjectCorrelationId ?? holder ?? digitalCredential.kmsKeyRef,
237
- kmsKeyRef: digitalCredential.kmsKeyRef,
238
- })
239
- }
129
+ const queryResult = DcqlQuery.query(request.dcqlQuery, Array.from(dcqlCredentialsWithCredentials.keys()))
130
+
131
+ if (!queryResult.can_be_satisfied) {
132
+ return Promise.reject(Error('Credentials do not match required query request'))
133
+ }
134
+
135
+ const presentation: DcqlPresentation.Output = {}
136
+ const uniqueCredentials = Array.from(dcqlCredentialsWithCredentials.values())
137
+ for (const [key, value] of Object.entries(queryResult.credential_matches)) {
138
+ if (value.success) {
139
+ const matchedCredentials = value.valid_credentials.map((cred) => uniqueCredentials[cred.input_credential_index])
140
+ const vc = matchedCredentials[0] // taking the first match for now //uniqueCredentials[value.input_credential_index]
141
+ if (!vc) {
142
+ continue
143
+ }
144
+ const originalVc = retrieveEncodedCredential(vc as UniqueDigitalCredential) // TODO this is not nice // also always a UniqueDigitalCredential
145
+ if (!originalVc) {
146
+ continue
240
147
  }
241
- console.log(`Identifier`, identifier)
242
-
243
- const dcqlRepresentations: DcqlCredential[] = []
244
- vcs.forEach((vc: UniqueDigitalCredential | OriginalVerifiableCredential) => {
245
- const rep = convertToDcqlCredentials(vc, args.hasher)
246
- if (rep) {
247
- dcqlRepresentations.push(rep)
248
- }
148
+ // FIXME SSISDK-44
149
+ const decodedSdJwt = await CredentialMapper.decodeSdJwtVcAsync(originalVc as string, defaultGenerateDigest)
150
+ const updatedSdJwt = updateSdJwtCredential(decodedSdJwt, request.requestObject?.getPayload()?.nonce, domain)
151
+
152
+ const presentationResult = await context.agent.createSdJwtPresentation({
153
+ presentation: updatedSdJwt.compactSdJwtVc,
154
+ kb: {
155
+ payload: {
156
+ ...updatedSdJwt.kbJwt?.payload,
157
+ // FIXME SSISDK-44
158
+ nonce: updatedSdJwt.kbJwt?.payload.nonce ?? request.requestObject!.getPayload()!.nonce,
159
+ // FIXME SSISDK-44
160
+ aud: updatedSdJwt.kbJwt?.payload.aud ?? domain,
161
+ iat: updatedSdJwt.kbJwt?.payload?.iat ?? Math.floor(Date.now() / 1000 - CLOCK_SKEW),
162
+ },
163
+ },
249
164
  })
250
165
 
251
- const queryResult = DcqlQuery.query(request.dcqlQuery, dcqlRepresentations)
252
- const presentation: Record<string, DcqlCredentialPresentation> = {}
253
-
254
- for (const [key, value] of Object.entries(queryResult.credential_matches)) {
255
- const allMatches = Array.isArray(value) ? value : [value]
256
- allMatches.forEach((match) => {
257
- if (match.success) {
258
- const originalCredential = getOriginalVerifiableCredential(vcs[match.input_credential_index])
259
- if (!originalCredential) {
260
- throw new Error(`Index ${match.input_credential_index} out of range in credentials array`)
261
- }
262
- presentation[key] =
263
- (originalCredential as any)['compactSdJwtVc'] !== undefined ? (originalCredential as any).compactSdJwtVc : originalCredential
264
- }
265
- })
166
+ if (originalVc) {
167
+ presentation[key] = presentationResult.presentation
266
168
  }
169
+ }
170
+ }
267
171
 
268
- const response = session.sendAuthorizationResponse({
269
- responseSignerOpts: identifier,
270
- ...{ dcqlQuery: { dcqlPresentation: DcqlPresentation.parse(presentation) } },
271
- })
172
+ const dcqlPresentation = DcqlPresentation.parse(presentation)
272
173
 
273
- logger.debug(`Response: `, response)
174
+ const response = session.sendAuthorizationResponse({
175
+ responseSignerOpts: identifier,
176
+ dcqlResponse: {
177
+ dcqlPresentation,
178
+ },
179
+ })
274
180
 
275
- return response
276
- }
277
- }
278
- throw Error('Presentation Definition or DCQL is required')
181
+ logger.debug(`Response: `, response)
182
+ return response
279
183
  }
280
184
 
281
- function buildPartialPD(
282
- inputDescriptor: InputDescriptorV1 | InputDescriptorV2,
283
- presentationDefinition: PresentationDefinitionV1 | PresentationDefinitionV2,
284
- ): IPresentationDefinition {
285
- return {
286
- ...presentationDefinition,
287
- input_descriptors: [inputDescriptor],
288
- } as IPresentationDefinition
185
+ const retrieveEncodedCredential = (credential: UniqueDigitalCredential): OriginalVerifiableCredential | undefined => {
186
+ return credential.originalVerifiableCredential !== undefined &&
187
+ credential.originalVerifiableCredential !== null &&
188
+ (credential?.originalVerifiableCredential as SdJwtDecodedVerifiableCredential)?.compactSdJwtVc !== undefined &&
189
+ (credential?.originalVerifiableCredential as SdJwtDecodedVerifiableCredential)?.compactSdJwtVc !== null
190
+ ? (credential.originalVerifiableCredential as SdJwtDecodedVerifiableCredential).compactSdJwtVc
191
+ : credential.originalVerifiableCredential
289
192
  }
290
193
 
291
- export const getSelectableCredentials = async (
292
- presentationDefinition: IPresentationDefinition,
293
- context: RequiredContext,
294
- ): Promise<SelectableCredentialsMap> => {
295
- const agentContext = { ...context, agent: context.agent as SuitableCredentialAgents }
194
+ export const getSelectableCredentials = async (dcqlQuery: DcqlQuery, context: RequiredContext): Promise<SelectableCredentialsMap> => {
195
+ const agentContext = { ...context, agent: context.agent }
296
196
  const { agent } = agentContext
297
- const pex = new PEX()
298
-
299
197
  const uniqueVerifiableCredentials = await agent.crsGetUniqueCredentials({
300
198
  filter: verifiableCredentialForRoleFilter(CredentialRole.HOLDER),
301
199
  })
302
- const credentialBranding = await agent.ibGetCredentialBranding()
303
-
200
+ const branding = await agent.ibGetCredentialBranding()
201
+ const dcqlCredentialsWithCredentials = new Map(uniqueVerifiableCredentials.map((vc) => [convertToDcqlCredentials(vc), vc]))
202
+ const queryResult = DcqlQuery.query(dcqlQuery, Array.from(dcqlCredentialsWithCredentials.keys()))
203
+ const uniqueCredentials = Array.from(dcqlCredentialsWithCredentials.values())
304
204
  const selectableCredentialsMap: SelectableCredentialsMap = new Map()
305
205
 
306
- for (const inputDescriptor of presentationDefinition.input_descriptors) {
307
- const partialPD = buildPartialPD(inputDescriptor, presentationDefinition)
308
- const originalCredentials = uniqueVerifiableCredentials.map((uniqueVC) => {
309
- return CredentialMapper.storedCredentialToOriginalFormat(uniqueVC.originalVerifiableCredential!) // ( ! is valid for verifiableCredentialForRoleFilter )
310
- })
311
- const selectionResults = pex.selectFrom(partialPD, originalCredentials)
206
+ for (const [key, value] of Object.entries(queryResult.credential_matches)) {
207
+ if (!value.valid_credentials) {
208
+ continue
209
+ }
312
210
 
313
- const selectableCredentials: Array<SelectableCredential> = []
314
- for (const selectedCredential of selectionResults.verifiableCredential || []) {
315
- const filteredUniqueVC = uniqueVerifiableCredentials.find((uniqueVC) => {
316
- const proof = uniqueVC.uniformVerifiableCredential!.proof
317
- return Array.isArray(proof) ? proof.some((proofItem) => proofItem.jwt === selectedCredential) : proof.jwt === selectedCredential
211
+ const mapSelectableCredentialPromises = value.valid_credentials.map(async (cred) => {
212
+ const matchedCredential = uniqueCredentials[cred.input_credential_index]
213
+ const credentialBranding = branding.filter((cb) => cb.vcHash === matchedCredential.hash)
214
+ const issuerPartyIdentity = await agent.cmGetContacts({
215
+ filter: [{ identities: { identifier: { correlationId: matchedCredential.uniformVerifiableCredential!.issuerDid } } }],
216
+ })
217
+ const subjectPartyIdentity = await agent.cmGetContacts({
218
+ filter: [{ identities: { identifier: { correlationId: matchedCredential.uniformVerifiableCredential!.subjectDid } } }],
318
219
  })
319
220
 
320
- if (filteredUniqueVC) {
321
- const filteredCredentialBrandings = credentialBranding.filter((cb) => cb.vcHash === filteredUniqueVC.hash)
322
- const issuerPartyIdentity = await agent.cmGetContacts({
323
- filter: [{ identities: { identifier: { correlationId: filteredUniqueVC.uniformVerifiableCredential!.issuerDid } } }],
324
- })
325
- const subjectPartyIdentity = await agent.cmGetContacts({
326
- filter: [{ identities: { identifier: { correlationId: filteredUniqueVC.uniformVerifiableCredential!.subjectDid } } }],
327
- })
328
-
329
- selectableCredentials.push({
330
- credential: filteredUniqueVC,
331
- credentialBranding: filteredCredentialBrandings[0]?.localeBranding,
332
- issuerParty: issuerPartyIdentity?.[0],
333
- subjectParty: subjectPartyIdentity?.[0],
334
- })
221
+ return {
222
+ credential: matchedCredential,
223
+ credentialBranding: credentialBranding[0]?.localeBranding,
224
+ issuerParty: issuerPartyIdentity?.[0],
225
+ subjectParty: subjectPartyIdentity?.[0],
335
226
  }
336
- }
337
- selectableCredentialsMap.set(inputDescriptor.id, selectableCredentials)
227
+ })
228
+
229
+ const selectableCredentials: Array<SelectableCredential> = await Promise.all(mapSelectableCredentialPromises)
230
+ selectableCredentialsMap.set(key, selectableCredentials)
338
231
  }
232
+
339
233
  return selectableCredentialsMap
340
234
  }
341
235
 
@@ -352,3 +246,33 @@ export const translateCorrelationIdToName = async (correlationId: string, contex
352
246
 
353
247
  return contacts[0].contact.displayName
354
248
  }
249
+
250
+ const updateSdJwtCredential = (
251
+ credential: SdJwtDecodedVerifiableCredential,
252
+ nonce?: string,
253
+ aud?: string,
254
+ ): PartialSdJwtDecodedVerifiableCredential => {
255
+ const sdJwtCredential = credential as SdJwtDecodedVerifiableCredential
256
+
257
+ // extract sd_alg or default to sha-256
258
+ const hashAlg = sdJwtCredential.signedPayload._sd_alg ?? 'sha-256'
259
+ const sdHash = calculateSdHash(sdJwtCredential.compactSdJwtVc, hashAlg, defaultGenerateDigest)
260
+
261
+ const kbJwt = {
262
+ // alg MUST be set by the signer
263
+ header: {
264
+ typ: 'kb+jwt',
265
+ },
266
+ payload: {
267
+ iat: Math.floor(new Date().getTime() / 1000),
268
+ sd_hash: sdHash,
269
+ ...(nonce && { nonce }),
270
+ ...(aud && { aud }),
271
+ },
272
+ } satisfies PartialSdJwtKbJwt
273
+
274
+ return {
275
+ ...sdJwtCredential,
276
+ kbJwt,
277
+ } satisfies PartialSdJwtDecodedVerifiableCredential
278
+ }