@docknetwork/wallet-sdk-core 1.7.7-alpha.0 → 1.9.0

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.
Files changed (76) hide show
  1. package/lib/cloud-wallet.d.ts +79 -3
  2. package/lib/cloud-wallet.d.ts.map +1 -1
  3. package/lib/cloud-wallet.js +147 -14
  4. package/lib/cloud-wallet.js.map +1 -1
  5. package/lib/credential-provider.d.ts.map +1 -1
  6. package/lib/credential-provider.js +10 -4
  7. package/lib/credential-provider.js.map +1 -1
  8. package/lib/delegation/delegation-chain.d.ts +8 -0
  9. package/lib/delegation/delegation-chain.d.ts.map +1 -0
  10. package/lib/delegation/delegation-chain.js +33 -0
  11. package/lib/delegation/delegation-chain.js.map +1 -0
  12. package/lib/delegation/delegation-fixtures.d.ts +69 -0
  13. package/lib/delegation/delegation-fixtures.d.ts.map +1 -0
  14. package/lib/delegation/delegation-fixtures.js +553 -0
  15. package/lib/delegation/delegation-fixtures.js.map +1 -0
  16. package/lib/delegation/delegation-issuance.d.ts +19 -0
  17. package/lib/delegation/delegation-issuance.d.ts.map +1 -0
  18. package/lib/delegation/delegation-issuance.js +60 -0
  19. package/lib/delegation/delegation-issuance.js.map +1 -0
  20. package/lib/delegation/delegation-offer.d.ts +84 -0
  21. package/lib/delegation/delegation-offer.d.ts.map +1 -0
  22. package/lib/delegation/delegation-offer.js +349 -0
  23. package/lib/delegation/delegation-offer.js.map +1 -0
  24. package/lib/delegation/delegation-policy-validation.d.ts +28 -0
  25. package/lib/delegation/delegation-policy-validation.d.ts.map +1 -0
  26. package/lib/delegation/delegation-policy-validation.js +170 -0
  27. package/lib/delegation/delegation-policy-validation.js.map +1 -0
  28. package/lib/delegation/delegation-policy.d.ts +21 -0
  29. package/lib/delegation/delegation-policy.d.ts.map +1 -0
  30. package/lib/delegation/delegation-policy.js +73 -0
  31. package/lib/delegation/delegation-policy.js.map +1 -0
  32. package/lib/delegation/delegation-tree.d.ts +17 -0
  33. package/lib/delegation/delegation-tree.d.ts.map +1 -0
  34. package/lib/delegation/delegation-tree.js +58 -0
  35. package/lib/delegation/delegation-tree.js.map +1 -0
  36. package/lib/delegation/delegation-types.d.ts +56 -0
  37. package/lib/delegation/delegation-types.d.ts.map +1 -0
  38. package/lib/delegation/delegation-types.js +3 -0
  39. package/lib/delegation/delegation-types.js.map +1 -0
  40. package/lib/delegation/delegation-utils.d.ts +3 -0
  41. package/lib/delegation/delegation-utils.d.ts.map +1 -0
  42. package/lib/delegation/delegation-utils.js +10 -0
  43. package/lib/delegation/delegation-utils.js.map +1 -0
  44. package/lib/did-provider.d.ts +2 -1
  45. package/lib/did-provider.d.ts.map +1 -1
  46. package/lib/did-provider.js +11 -7
  47. package/lib/did-provider.js.map +1 -1
  48. package/lib/message-provider.js +1 -1
  49. package/lib/message-provider.js.map +1 -1
  50. package/lib/verification-controller.d.ts +30 -11
  51. package/lib/verification-controller.d.ts.map +1 -1
  52. package/lib/verification-controller.js +372 -68
  53. package/lib/verification-controller.js.map +1 -1
  54. package/package.json +3 -3
  55. package/src/cloud-wallet.test.js +369 -0
  56. package/src/cloud-wallet.ts +206 -18
  57. package/src/credential-provider.ts +13 -4
  58. package/src/delegation/delegation-chain.test.ts +64 -0
  59. package/src/delegation/delegation-chain.ts +34 -0
  60. package/src/delegation/delegation-fixtures.ts +552 -0
  61. package/src/delegation/delegation-issuance.ts +92 -0
  62. package/src/delegation/delegation-offer.ts +488 -0
  63. package/src/delegation/delegation-policy-validation.test.ts +237 -0
  64. package/src/delegation/delegation-policy-validation.ts +281 -0
  65. package/src/delegation/delegation-policy.ts +100 -0
  66. package/src/delegation/delegation-tree.test.ts +110 -0
  67. package/src/delegation/delegation-tree.ts +60 -0
  68. package/src/delegation/delegation-types.ts +65 -0
  69. package/src/delegation/delegation-utils.ts +10 -0
  70. package/src/did-provider.ts +10 -6
  71. package/src/globals.d.ts +6 -0
  72. package/src/message-provider.ts +1 -1
  73. package/src/verification-controller.test.ts +23 -0
  74. package/src/verification-controller.ts +534 -82
  75. package/tsconfig.build.json +2 -1
  76. package/tsconfig.build.tsbuildinfo +1 -1
@@ -3,6 +3,7 @@ import {pexService} from '@docknetwork/wallet-sdk-wasm/src/services/pex';
3
3
  import {credentialServiceRPC} from '@docknetwork/wallet-sdk-wasm/src/services/credential';
4
4
  import {
5
5
  createCredentialProvider,
6
+ CredentialStatus,
6
7
  ICredentialProvider,
7
8
  } from './credential-provider';
8
9
  import {IWallet} from './types';
@@ -21,17 +22,55 @@ export enum VerificationStatus {
21
22
  SelectingCredentials = 'SelectingCredentials',
22
23
  }
23
24
 
24
- function isRangeProofTemplate(templateJSON) {
25
- return templateJSON.proving_key;
26
- }
27
-
28
25
  type CredentialId = string;
29
26
  type CredentialSelection = {
30
27
  credential: any;
28
+ /**
29
+ * Optional list of credential attributes to reveal in the presentation.
30
+ * When omitted, the credential-sdk automatically determines which attributes
31
+ * to reveal based on the PEX (Presentation Exchange) template requirements.
32
+ * This allows generating a default presentation without manual attribute selection.
33
+ */
31
34
  attributesToReveal?: string[];
32
35
  };
33
36
  type CredentialSelectionMap = Map<CredentialId, CredentialSelection>;
34
37
 
38
+ export interface IVerificationController {
39
+ emitter: EventEmitter;
40
+ selectedCredentials: CredentialSelectionMap;
41
+ getStatus: () => VerificationStatus;
42
+ getStatusData: () => any;
43
+ submitPresentation: (presentation: any) => Promise<any>;
44
+ getSelectedDID: () => string | null;
45
+ setSelectedDID: (did: string) => void;
46
+ start: (params: {template: string | any}) => Promise<void>;
47
+ isBBSPlusCredential: (credential: any) => Promise<boolean>;
48
+ loadCredentials: () => Promise<void>;
49
+ getFilteredCredentials: () => any[];
50
+ createPresentation: () => Promise<any>;
51
+ createDefaultPresentation: () => Promise<any>;
52
+ evaluatePresentation: (presentation: any) => {
53
+ isValid: boolean;
54
+ errors: any[];
55
+ warnings: any[];
56
+ };
57
+ getRequirementGroups: () => Array<{
58
+ descriptorKey: string;
59
+ descriptorName: string;
60
+ candidates: any[];
61
+ }>;
62
+ getSelectedCredentialsByDescriptor: () => any[];
63
+ getCredentialOptionsForDescriptor: (credentialId: string) => Promise<any>;
64
+ switchCredential: (
65
+ currentCredentialId: string,
66
+ replacementCredentialId: string,
67
+ ) => Promise<void>;
68
+ getRequestedAttributes: (credentialId: string) => any[];
69
+ getCredentialStatus: (credentialId: string) => Promise<any>;
70
+ canSwitchCredential: (credentialId: string) => Promise<boolean>;
71
+ getTemplateJSON: () => any;
72
+ }
73
+
35
74
  export function createVerificationController({
36
75
  wallet,
37
76
  credentialProvider,
@@ -40,7 +79,7 @@ export function createVerificationController({
40
79
  wallet: IWallet;
41
80
  credentialProvider?: ICredentialProvider;
42
81
  didProvider?: IDIDProvider;
43
- }) {
82
+ }): IVerificationController {
44
83
  const emitter = new EventEmitter();
45
84
  let templateJSON = null;
46
85
  let status = VerificationStatus.Started;
@@ -50,9 +89,9 @@ export function createVerificationController({
50
89
  */
51
90
  let statusData = null;
52
91
  let filteredCredentials = [];
92
+ let filteredMatches = [];
53
93
  let selectedCredentials: CredentialSelectionMap = new Map();
54
94
  let selectedDID = null;
55
- let provingKey = null;
56
95
 
57
96
  if (!credentialProvider) {
58
97
  credentialProvider = createCredentialProvider({wallet});
@@ -62,23 +101,6 @@ export function createVerificationController({
62
101
  didProvider = createDIDProvider({wallet});
63
102
  }
64
103
 
65
- async function fetchProvingKey(templateJSON: any) {
66
- if (templateJSON.proving_key) {
67
- setState(VerificationStatus.FetchingProvingKey);
68
- try {
69
- provingKey = await axios
70
- .get(templateJSON.proving_key)
71
- .then(res => res.data);
72
- } catch (err) {
73
- setState(VerificationStatus.Error, {
74
- message: 'failed_to_fetch_proving_key',
75
- });
76
-
77
- throw err;
78
- }
79
- }
80
- }
81
-
82
104
  async function start({template}: {template: string | any}) {
83
105
  setState(VerificationStatus.LoadingTemplate);
84
106
 
@@ -96,7 +118,6 @@ export function createVerificationController({
96
118
  selectedDID = dids[0].didDocument.id;
97
119
  templateJSON = await getJSON(template);
98
120
 
99
- await fetchProvingKey(templateJSON);
100
121
  await loadCredentials();
101
122
 
102
123
  setState(VerificationStatus.SelectingCredentials);
@@ -127,6 +148,7 @@ export function createVerificationController({
127
148
  });
128
149
 
129
150
  filteredCredentials = result.verifiableCredential;
151
+ filteredMatches = result.matches || [];
130
152
  } catch (err) {
131
153
  console.error(
132
154
  `Unable to filter credentials using the template: \n ${JSON.stringify(
@@ -154,70 +176,354 @@ export function createVerificationController({
154
176
  return credentialServiceRPC.isKvacCredential({credential});
155
177
  }
156
178
 
157
- async function createPresentation() {
158
- assert(!!selectedDID, 'No DID selected');
159
- assert(!!selectedCredentials.size, 'No credentials selected');
179
+ async function deriveNonBbsCredentials(sdJwtSelections, regularSelections) {
180
+ const credentials = [];
160
181
 
161
- if (isRangeProofTemplate(templateJSON)) {
162
- // TODO: Implement proving key usage for range-proofs
163
- assert(!!provingKey, 'No proving key found');
182
+ for (const sel of sdJwtSelections) {
183
+ const derived = await credentialServiceRPC.createSDJWTPresentation({
184
+ attributesToReveal: sel.attributesToReveal,
185
+ credential: sel.credential._sd_jwt.encoded,
186
+ });
187
+ credentials.push(derived);
164
188
  }
165
189
 
166
- const credentials = [];
190
+ for (const sel of regularSelections) {
191
+ credentials.push(sel.credential);
192
+ }
167
193
 
168
- for (const credentialSelection of selectedCredentials.values()) {
169
- const isBBS = await isBBSPlusCredential(credentialSelection.credential);
170
- const isKVAC = await isKvacCredential(credentialSelection.credential);
194
+ return credentials;
195
+ }
171
196
 
172
- if (credentialSelection.credential._sd_jwt) {
173
- const derivedCredential =
174
- await credentialServiceRPC.createSDJWTPresentation({
175
- attributesToReveal: credentialSelection.attributesToReveal,
176
- credential: credentialSelection.credential._sd_jwt.encoded,
177
- });
178
-
179
- credentials.push(derivedCredential);
180
- } else if (isBBS || isKVAC) {
181
- // derive credential
182
- const derivedCredentials =
183
- await credentialServiceRPC.deriveVCFromPresentation({
184
- proofRequest: templateJSON,
185
-
186
- credentials: [
187
- {
188
- credential: credentialSelection.credential,
189
- witness: await credentialProvider.getMembershipWitness(credentialSelection.credential.id),
190
- attributesToReveal: [
191
- ...(credentialSelection.attributesToReveal || []),
192
- 'id',
193
- ],
194
- },
195
- ],
196
- });
197
+ function getKeyId(keyDoc) {
198
+ return keyDoc.controller.startsWith('did:key:')
199
+ ? keyDoc.id
200
+ : `${keyDoc.controller}#keys-1`;
201
+ }
202
+
203
+ async function assembleSignedPresentation(credentials, keyDoc) {
204
+ return credentialServiceRPC.createPresentation({
205
+ credentials,
206
+ challenge: templateJSON.nonce,
207
+ keyDoc,
208
+ id: getKeyId(keyDoc),
209
+ domain: 'dock.io',
210
+ });
211
+ }
197
212
 
198
- console.log('Credential derived');
213
+ function getRequirementGroups() {
214
+ if (filteredMatches.length === 0) {
215
+ return [
216
+ {
217
+ descriptorKey: 'default',
218
+ descriptorName: 'default',
219
+ candidates: [...filteredCredentials],
220
+ },
221
+ ];
222
+ }
199
223
 
200
- credentials.push(derivedCredentials[0]);
224
+ const groups: Array<{
225
+ descriptorKey: string;
226
+ descriptorName: string;
227
+ candidates: any[];
228
+ }> = [];
229
+ const groupKeyFn = match =>
230
+ match.from ? JSON.stringify(match.from) : match.name || match.id || '';
231
+ const seen = new Map<string, number>();
232
+
233
+ for (const match of filteredMatches) {
234
+ const key = groupKeyFn(match);
235
+ const candidateIndices: number[] = [];
236
+ for (const path of match.vc_path || []) {
237
+ const indexMatch = path.match(/\[(\d+)\]/);
238
+ if (indexMatch) {
239
+ candidateIndices.push(parseInt(indexMatch[1], 10));
240
+ }
241
+ }
242
+ const candidates = candidateIndices
243
+ .map(idx => filteredCredentials[idx])
244
+ .filter(Boolean);
245
+
246
+ if (match.from || !seen.has(key)) {
247
+ seen.set(key, groups.length);
248
+ groups.push({
249
+ descriptorKey: key,
250
+ descriptorName: match.name || match.id || key,
251
+ candidates,
252
+ });
201
253
  } else {
202
- credentials.push(credentialSelection.credential);
254
+ const existing = groups[seen.get(key)];
255
+ for (const cred of candidates) {
256
+ if (!existing.candidates.some(c => c.id === cred.id)) {
257
+ existing.candidates.push(cred);
258
+ }
259
+ }
203
260
  }
204
261
  }
205
262
 
263
+ return groups;
264
+ }
265
+
266
+ async function filterValidCredentials(candidates: any[]) {
267
+ const results = await Promise.all(
268
+ candidates.map(async cred => {
269
+ const statusResult = await credentialProvider.getCredentialStatus(cred);
270
+ return {
271
+ credential: cred,
272
+ status: statusResult.status,
273
+ };
274
+ }),
275
+ );
276
+
277
+ return results
278
+ .filter(
279
+ r =>
280
+ r.status !== CredentialStatus.Revoked &&
281
+ r.status !== CredentialStatus.Invalid &&
282
+ r.status !== CredentialStatus.Expired,
283
+ )
284
+ .map(r => r.credential);
285
+ }
286
+
287
+ async function createDefaultPresentation() {
288
+ assert(filteredCredentials.length > 0, 'No filtered credentials available');
289
+
290
+ selectedCredentials.clear();
291
+
292
+ const groups = getRequirementGroups();
293
+ for (const group of groups) {
294
+ const validCandidates = await filterValidCredentials(group.candidates);
295
+ const chosen =
296
+ validCandidates.find(cred => !selectedCredentials.has(cred.id)) ||
297
+ validCandidates[0];
298
+ if (chosen) {
299
+ selectedCredentials.set(chosen.id, {credential: chosen});
300
+ }
301
+ }
302
+
303
+ assert(
304
+ selectedCredentials.size > 0,
305
+ 'No credentials could be selected for the presentation',
306
+ );
307
+
308
+ return createPresentation();
309
+ }
310
+
311
+ function getSelectedCredentialsByDescriptor() {
312
+ const groups = getRequirementGroups();
313
+
314
+ return groups.map(group => {
315
+ const selected =
316
+ group.candidates.find(cred => selectedCredentials.has(cred.id)) || null;
317
+ const alternatives = group.candidates.filter(
318
+ cred => !selected || cred.id !== selected.id,
319
+ );
320
+
321
+ return {
322
+ descriptorId: group.descriptorKey,
323
+ descriptorName: group.descriptorName,
324
+ selected,
325
+ alternatives,
326
+ };
327
+ });
328
+ }
329
+
330
+ async function getCredentialOptionsForDescriptor(credentialId: string) {
331
+ const groups = getRequirementGroups();
332
+ const group = groups.find(g =>
333
+ g.candidates.some(c => c.id === credentialId),
334
+ );
335
+
336
+ assert(
337
+ group,
338
+ `Credential ${credentialId} not found in any descriptor group`,
339
+ );
340
+
341
+ const selected = group.candidates.find(c => c.id === credentialId);
342
+ const allAlternatives = group.candidates.filter(c => c.id !== credentialId);
343
+ const alternatives = await filterValidCredentials(allAlternatives);
344
+
345
+ return {
346
+ descriptorId: group.descriptorKey,
347
+ descriptorName: group.descriptorName,
348
+ selected,
349
+ alternatives,
350
+ };
351
+ }
352
+
353
+ async function switchCredential(
354
+ currentCredentialId: string,
355
+ replacementCredentialId: string,
356
+ ) {
357
+ assert(
358
+ selectedCredentials.has(currentCredentialId),
359
+ `Credential ${currentCredentialId} is not currently selected`,
360
+ );
361
+
362
+ const options = await getCredentialOptionsForDescriptor(currentCredentialId);
363
+ const replacement = options.alternatives.find(
364
+ c => c.id === replacementCredentialId,
365
+ );
366
+
367
+ assert(
368
+ replacement,
369
+ `Credential ${replacementCredentialId} is not a valid replacement for ${currentCredentialId}`,
370
+ );
371
+
372
+ selectedCredentials.delete(currentCredentialId);
373
+ selectedCredentials.set(replacementCredentialId, {credential: replacement});
374
+
375
+ return createPresentation();
376
+ }
377
+
378
+ function getAttributesToRevealFromTemplate(credential) {
379
+ const definition = getPresentationDefinition();
380
+ if (!definition?.input_descriptors) {
381
+ return ['id'];
382
+ }
383
+
384
+ const attributes = ['id'];
385
+ for (const descriptor of definition.input_descriptors) {
386
+ const fields = descriptor.constraints?.fields || [];
387
+ for (const field of fields) {
388
+ if (!field.path) {
389
+ continue;
390
+ }
391
+ const paths = Array.isArray(field.path) ? field.path : [field.path];
392
+ for (const p of paths) {
393
+ const attr = p.replace('$.', '');
394
+ if (
395
+ attr &&
396
+ !attributes.includes(attr) &&
397
+ !attr.startsWith('type') &&
398
+ !attr.startsWith('issuer') &&
399
+ !attr.startsWith('@context') &&
400
+ !attr.startsWith('proof') &&
401
+ !attr.startsWith('credentialSchema') &&
402
+ !attr.startsWith('issuanceDate')
403
+ ) {
404
+ // Only include if the credential actually has this attribute
405
+ const value = attr
406
+ .split('.')
407
+ .reduce((obj, key) => obj?.[key], credential);
408
+ if (value !== undefined) {
409
+ attributes.push(attr);
410
+ }
411
+ break;
412
+ }
413
+ }
414
+ }
415
+ }
416
+
417
+ return attributes;
418
+ }
419
+
420
+ function ensureDescriptorMap(presentation) {
421
+ if (presentation?.presentation_submission?.descriptor_map?.length > 0) {
422
+ return presentation;
423
+ }
424
+
425
+ const definition = getPresentationDefinition();
426
+ if (!definition?.input_descriptors) {
427
+ return presentation;
428
+ }
429
+
430
+ const descriptorMap = definition.input_descriptors.map(
431
+ (descriptor, idx) => ({
432
+ id: descriptor.id,
433
+ format: 'ldp_vp',
434
+ path: `$.verifiableCredential[${idx}]`,
435
+ }),
436
+ );
437
+
438
+ presentation.presentation_submission = {
439
+ ...presentation.presentation_submission,
440
+ definition_id: definition.id,
441
+ descriptor_map: descriptorMap,
442
+ };
443
+
444
+ return presentation;
445
+ }
446
+
447
+ async function createPresentation() {
448
+ assert(!!selectedDID, 'No DID selected');
449
+ assert(!!selectedCredentials.size, 'No credentials selected');
450
+
206
451
  const didKeyPairList = await didProvider.getDIDKeyPairs();
207
452
  const keyDoc = didKeyPairList.find(doc => doc.controller === selectedDID);
208
-
209
453
  assert(keyDoc, `No key pair found for the selected DID ${selectedDID}`);
210
454
 
211
- const presentation = await credentialServiceRPC.createPresentation({
212
- credentials,
213
- challenge: templateJSON.nonce,
214
- keyDoc,
215
- id: keyDoc.controller.startsWith('did:key:')
216
- ? keyDoc.id
217
- : `${keyDoc.controller}#keys-1`,
218
- domain: 'dock.io',
219
- });
455
+ const sdJwtSelections = [];
456
+ const bbsKvacSelections = [];
457
+ const regularSelections = [];
458
+
459
+ for (const credentialSelection of selectedCredentials.values()) {
460
+ if (credentialSelection.credential._sd_jwt) {
461
+ sdJwtSelections.push(credentialSelection);
462
+ } else {
463
+ const isBBS = await isBBSPlusCredential(credentialSelection.credential);
464
+ const isKVAC = await isKvacCredential(credentialSelection.credential);
465
+ if (isBBS || isKVAC) {
466
+ bbsKvacSelections.push(credentialSelection);
467
+ } else {
468
+ regularSelections.push(credentialSelection);
469
+ }
470
+ }
471
+ }
472
+
473
+ if (bbsKvacSelections.length > 0) {
474
+ // When attributesToReveal is undefined, the credential-sdk will automatically
475
+ // determine which attributes to reveal based on the PEX template requirements.
476
+ // This enables generating a default presentation without manual attribute selection.
477
+ const credentialsWithWitness = await Promise.all(
478
+ bbsKvacSelections.map(async sel => {
479
+ return {
480
+ credential: sel.credential,
481
+ witness: await credentialProvider.getMembershipWitness(
482
+ sel.credential.id,
483
+ ),
484
+ attributesToReveal:
485
+ sel.attributesToReveal ||
486
+ getAttributesToRevealFromTemplate(sel.credential),
487
+ };
488
+ }),
489
+ );
220
490
 
491
+ // Derive each BBS+/KVAC credential separately, then assemble into a signed presentation.
492
+ // This approach uses deriveVCFromPresentation which properly handles range proof
493
+ // bound checks and produces presentations that the Truvera API can verify.
494
+ const derivedResults = await Promise.all(
495
+ credentialsWithWitness.map(c =>
496
+ credentialServiceRPC.deriveVCFromPresentation({
497
+ proofRequest: templateJSON,
498
+ credentials: [
499
+ {
500
+ credential: c.credential,
501
+ witness: c.witness,
502
+ attributesToReveal: c.attributesToReveal,
503
+ },
504
+ ],
505
+ }),
506
+ ),
507
+ );
508
+ const derivedCredentials = derivedResults.flat();
509
+
510
+ const nonBbsCredentials = await deriveNonBbsCredentials(
511
+ sdJwtSelections,
512
+ regularSelections,
513
+ );
514
+ const presentation = await assembleSignedPresentation(
515
+ [...derivedCredentials, ...nonBbsCredentials],
516
+ keyDoc,
517
+ );
518
+ return presentation;
519
+ }
520
+
521
+ // No BBS+/KVAC: handle SD-JWT and regular only
522
+ const credentials = await deriveNonBbsCredentials(
523
+ sdJwtSelections,
524
+ regularSelections,
525
+ );
526
+ const presentation = await assembleSignedPresentation(credentials, keyDoc);
221
527
  return presentation;
222
528
  }
223
529
 
@@ -245,13 +551,9 @@ export function createVerificationController({
245
551
  *
246
552
  * @param presentation
247
553
  */
248
- function evaluatePresentation(presentation): {
249
- isValid: boolean;
250
- errors: any[];
251
- warnings: any[];
252
- } {
554
+ function evaluatePresentation(presentation) {
253
555
  const definition = getPresentationDefinition();
254
- const result = credentialServiceRPC.evaluatePresentation({
556
+ const result = pexService.evaluatePresentation({
255
557
  presentation,
256
558
  presentationDefinition: definition,
257
559
  });
@@ -263,10 +565,152 @@ export function createVerificationController({
263
565
  };
264
566
  }
265
567
 
266
- function submitPresentation(presentation) {
267
- return axios
268
- .post(templateJSON.response_url, presentation)
269
- .then(res => res.data);
568
+ function getRequestedAttributes(credentialId: string) {
569
+ const definition = getPresentationDefinition();
570
+ if (!definition?.input_descriptors) {
571
+ return [];
572
+ }
573
+
574
+ const groups = getRequirementGroups();
575
+ const group = groups.find(g =>
576
+ g.candidates.some(c => c.id === credentialId),
577
+ );
578
+ if (!group) {
579
+ return [];
580
+ }
581
+
582
+ const credential = group.candidates.find(c => c.id === credentialId);
583
+ if (!credential) {
584
+ return [];
585
+ }
586
+
587
+ const descriptor = definition.input_descriptors.find(
588
+ d => (d.name || d.id) === group.descriptorName,
589
+ );
590
+ if (!descriptor?.constraints?.fields) {
591
+ return [];
592
+ }
593
+
594
+ const attributesToSkip = [
595
+ /^type/,
596
+ /^issuer/,
597
+ /^@context/,
598
+ /^proof/,
599
+ /^credentialSchema/,
600
+ /^issuanceDate/,
601
+ /^credentialStatus/,
602
+ /^cryptoVersion/,
603
+ ];
604
+
605
+ return descriptor.constraints.fields
606
+ .map(field => {
607
+ const paths = Array.isArray(field.path) ? field.path : [field.path];
608
+ let resolvedPath = null;
609
+ let value;
610
+
611
+ for (const p of paths) {
612
+ const cleanPath = p.replace('$.', '');
613
+ const pathParts = cleanPath.split('.');
614
+ let current = credential;
615
+ let found = true;
616
+ for (const part of pathParts) {
617
+ if (current && typeof current === 'object' && part in current) {
618
+ current = current[part];
619
+ } else {
620
+ found = false;
621
+ break;
622
+ }
623
+ }
624
+ if (found) {
625
+ resolvedPath = cleanPath;
626
+ value = current;
627
+ break;
628
+ }
629
+ }
630
+
631
+ if (!resolvedPath) {
632
+ return null;
633
+ }
634
+
635
+ if (attributesToSkip.some(regex => regex.test(resolvedPath))) {
636
+ return null;
637
+ }
638
+
639
+ const isRangeProof = !!(
640
+ field.filter &&
641
+ (field.filter.minimum !== undefined ||
642
+ field.filter.maximum !== undefined ||
643
+ field.filter.exclusiveMinimum !== undefined ||
644
+ field.filter.exclusiveMaximum !== undefined ||
645
+ field.filter.formatMinimum !== undefined ||
646
+ field.filter.formatMaximum !== undefined)
647
+ );
648
+
649
+ return {
650
+ name: resolvedPath,
651
+ value: isRangeProof ? null : value,
652
+ isRangeProof,
653
+ isOptional: field.optional === true,
654
+ ...(isRangeProof && {
655
+ min:
656
+ field.filter.minimum ??
657
+ field.filter.exclusiveMinimum ??
658
+ field.filter.formatMinimum,
659
+ max:
660
+ field.filter.maximum ??
661
+ field.filter.exclusiveMaximum ??
662
+ field.filter.formatMaximum,
663
+ }),
664
+ };
665
+ })
666
+ .filter(Boolean);
667
+ }
668
+
669
+ async function getCredentialStatus(credentialId: string) {
670
+ const groups = getRequirementGroups();
671
+ let credential = null;
672
+
673
+ for (const group of groups) {
674
+ credential = group.candidates.find(c => c.id === credentialId);
675
+ if (credential) {
676
+ break;
677
+ }
678
+ }
679
+
680
+ assert(
681
+ credential,
682
+ `Credential ${credentialId} not found in any descriptor group`,
683
+ );
684
+
685
+ return credentialProvider.isValid(credential);
686
+ }
687
+
688
+ async function canSwitchCredential(credentialId: string) {
689
+ try {
690
+ const options = await getCredentialOptionsForDescriptor(credentialId);
691
+ return options.alternatives.length > 0;
692
+ } catch {
693
+ return false;
694
+ }
695
+ }
696
+
697
+ async function submitPresentation(presentation, maxRetries = 3) {
698
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
699
+ try {
700
+ const res = await axios.post(templateJSON.response_url, presentation);
701
+ return res.data;
702
+ } catch (err) {
703
+ const httpStatus = err?.response?.status;
704
+ const isRetryable =
705
+ !httpStatus || httpStatus === 429 || httpStatus >= 500;
706
+
707
+ if (!isRetryable || attempt === maxRetries - 1) {
708
+ throw err;
709
+ }
710
+ const delay = Math.min(1000 * 2 ** attempt, 8000);
711
+ await new Promise(resolve => setTimeout(resolve, delay));
712
+ }
713
+ }
270
714
  }
271
715
 
272
716
  return {
@@ -283,8 +727,16 @@ export function createVerificationController({
283
727
  isBBSPlusCredential,
284
728
  loadCredentials,
285
729
  getFilteredCredentials,
730
+ createDefaultPresentation,
286
731
  createPresentation,
287
732
  evaluatePresentation,
733
+ getRequirementGroups,
734
+ getSelectedCredentialsByDescriptor,
735
+ getCredentialOptionsForDescriptor,
736
+ switchCredential,
737
+ getRequestedAttributes,
738
+ getCredentialStatus,
739
+ canSwitchCredential,
288
740
  getTemplateJSON() {
289
741
  return templateJSON;
290
742
  },