@sd-jwt/sd-jwt-vc 0.17.2-next.1 → 0.17.2-next.10

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.
@@ -4,7 +4,7 @@ import { digest, generateSalt } from '@sd-jwt/crypto-nodejs';
4
4
  import type { DisclosureFrame, Signer, Verifier } from '@sd-jwt/types';
5
5
  import { HttpResponse, http } from 'msw';
6
6
  import { setupServer } from 'msw/node';
7
- import { afterAll, beforeAll, describe, expect, test } from 'vitest';
7
+ import { afterAll, beforeAll, describe, expect, test, vitest } from 'vitest';
8
8
  import { SDJwtVcInstance } from '..';
9
9
  import type { SdJwtVcPayload } from '../sd-jwt-vc-payload';
10
10
  import type { TypeMetadataFormat } from '../sd-jwt-vc-type-metadata-format';
@@ -15,6 +15,131 @@ const exampleVctm = {
15
15
  description: 'An example credential type',
16
16
  };
17
17
 
18
+ const baseVctm: TypeMetadataFormat = {
19
+ vct: 'http://example.com/base',
20
+ name: 'BaseCredentialType',
21
+ description: 'A base credential type',
22
+ claims: [
23
+ {
24
+ path: ['firstName'],
25
+ display: [{ lang: 'en', label: 'First Name' }],
26
+ },
27
+ ],
28
+ display: [
29
+ {
30
+ lang: 'en',
31
+ name: 'Base Credential',
32
+ description: 'Base description',
33
+ },
34
+ ],
35
+ };
36
+
37
+ const extendingVctm: TypeMetadataFormat = {
38
+ vct: 'http://example.com/extending',
39
+ name: 'ExtendingCredentialType',
40
+ description: 'A credential type that extends the base',
41
+ extends: 'http://example.com/base',
42
+ claims: [
43
+ {
44
+ path: ['lastName'],
45
+ display: [{ lang: 'en', label: 'Last Name' }],
46
+ },
47
+ ],
48
+ display: [
49
+ {
50
+ lang: 'en',
51
+ name: 'Extended Credential',
52
+ description: 'Extended description',
53
+ },
54
+ {
55
+ lang: 'de',
56
+ name: 'Erweiterte Berechtigung',
57
+ description: 'Erweiterte Beschreibung',
58
+ },
59
+ ],
60
+ };
61
+
62
+ const middleVctm: TypeMetadataFormat = {
63
+ vct: 'http://example.com/middle',
64
+ name: 'MiddleCredentialType',
65
+ description: 'Middle type in chain',
66
+ extends: 'http://example.com/extending',
67
+ claims: [
68
+ {
69
+ path: ['age'],
70
+ display: [{ lang: 'en', label: 'Age' }],
71
+ },
72
+ ],
73
+ };
74
+
75
+ const overridingVctm: TypeMetadataFormat = {
76
+ vct: 'http://example.com/overriding',
77
+ name: 'OverridingCredentialType',
78
+ description: 'A credential type that overrides a claim from the base',
79
+ extends: 'http://example.com/base',
80
+ claims: [
81
+ {
82
+ path: ['firstName'],
83
+ display: [{ lang: 'en', label: 'Given Name' }], // Override with different label
84
+ sd: 'always' as const,
85
+ },
86
+ {
87
+ path: ['middleName'],
88
+ display: [{ lang: 'en', label: 'Middle Name' }],
89
+ },
90
+ ],
91
+ };
92
+
93
+ const circularVctm: TypeMetadataFormat = {
94
+ vct: 'http://example.com/circular',
95
+ name: 'CircularCredentialType',
96
+ extends: 'http://example.com/circular',
97
+ };
98
+
99
+ const deepVctm: TypeMetadataFormat = {
100
+ vct: 'http://example.com/deep',
101
+ name: 'DeepCredentialType',
102
+ extends: 'http://example.com/middle',
103
+ };
104
+
105
+ const baseWithSdAlways: TypeMetadataFormat = {
106
+ vct: 'http://example.com/base-sd-always',
107
+ name: 'BaseWithSdAlways',
108
+ claims: [
109
+ {
110
+ path: ['sensitiveData'],
111
+ sd: 'always' as const,
112
+ display: [{ lang: 'en', label: 'Sensitive Data' }],
113
+ },
114
+ ],
115
+ };
116
+
117
+ const invalidExtendingSdChange: TypeMetadataFormat = {
118
+ vct: 'http://example.com/invalid-sd-change',
119
+ name: 'InvalidSdChange',
120
+ extends: 'http://example.com/base-sd-always',
121
+ claims: [
122
+ {
123
+ path: ['sensitiveData'],
124
+ sd: 'never' as const, // Invalid: trying to change from 'always' to 'never'
125
+ display: [{ lang: 'en', label: 'Sensitive Data' }],
126
+ },
127
+ ],
128
+ };
129
+
130
+ const validExtendingSdChange: TypeMetadataFormat = {
131
+ vct: 'http://example.com/valid-sd-change',
132
+ name: 'ValidSdChange',
133
+ extends: 'http://example.com/base',
134
+ claims: [
135
+ {
136
+ path: ['firstName'],
137
+ sd: 'always' as const, // Valid: base doesn't have sd or has 'allowed'
138
+ display: [{ lang: 'en', label: 'First Name' }],
139
+ },
140
+ ],
141
+ };
142
+
18
143
  const restHandlers = [
19
144
  http.get('http://example.com/example', () => {
20
145
  const res: TypeMetadataFormat = exampleVctm;
@@ -27,6 +152,40 @@ const restHandlers = [
27
152
  }, 10000);
28
153
  });
29
154
  }),
155
+ http.get('http://example.com/base', () => {
156
+ return HttpResponse.json(baseVctm);
157
+ }),
158
+ http.get('http://example.com/extending', () => {
159
+ return HttpResponse.json(extendingVctm);
160
+ }),
161
+ http.get('http://example.com/middle', () => {
162
+ return HttpResponse.json(middleVctm);
163
+ }),
164
+ http.get('http://example.com/overriding', () => {
165
+ return HttpResponse.json(overridingVctm);
166
+ }),
167
+ http.get('http://example.com/circular', () => {
168
+ return HttpResponse.json(circularVctm);
169
+ }),
170
+ http.get('http://example.com/deep', () => {
171
+ return HttpResponse.json(deepVctm);
172
+ }),
173
+ http.get('http://example.com/base-sd-always', () => {
174
+ return HttpResponse.json(baseWithSdAlways);
175
+ }),
176
+ http.get('http://example.com/invalid-sd-change', () => {
177
+ return HttpResponse.json(invalidExtendingSdChange);
178
+ }),
179
+ http.get('http://example.com/valid-sd-change', () => {
180
+ return HttpResponse.json(validExtendingSdChange);
181
+ }),
182
+ http.get('http://example.com/invalid', () => {
183
+ // Return invalid type metadata (missing required 'vct' field)
184
+ return HttpResponse.json({
185
+ name: 'InvalidCredentialType',
186
+ description: 'Missing required vct field',
187
+ });
188
+ }),
30
189
  ];
31
190
 
32
191
  //this value could be generated on demand to make it easier when changing the values
@@ -85,22 +244,52 @@ describe('App', () => {
85
244
  afterEach(() => server.resetHandlers());
86
245
 
87
246
  test('VCT Validation', async () => {
247
+ // The method is private, so TS complains, but you can use spies on private method just fine.
248
+ // @ts-expect-error
249
+ const validateIntegritySpy = vitest.spyOn(sdjwt, 'validateIntegrity');
250
+
88
251
  const expectedPayload: SdJwtVcPayload = {
89
252
  iat,
90
253
  iss,
91
254
  vct,
92
- 'vct#Integrity': vctIntegrity,
255
+ 'vct#integrity': vctIntegrity,
93
256
  ...claims,
94
257
  };
258
+
95
259
  const encodedSdjwt = await sdjwt.issue(
96
260
  expectedPayload,
97
261
  disclosureFrame as unknown as DisclosureFrame<SdJwtVcPayload>,
98
262
  );
99
263
 
100
264
  await sdjwt.verify(encodedSdjwt);
265
+
266
+ // Ensure validateIntegrity method was called
267
+ expect(validateIntegritySpy).toHaveBeenCalledWith(
268
+ expect.any(Response),
269
+ vct,
270
+ vctIntegrity,
271
+ );
101
272
  });
102
273
 
103
- test('VCT from JWT header Validation', async () => {
274
+ test('VCT Validation with timeout', async () => {
275
+ const vct = 'http://example.com/timeout';
276
+ const expectedPayload: SdJwtVcPayload = {
277
+ iat,
278
+ iss,
279
+ vct,
280
+ ...claims,
281
+ };
282
+ const encodedSdjwt = await sdjwt.issue(
283
+ expectedPayload,
284
+ disclosureFrame as unknown as DisclosureFrame<SdJwtVcPayload>,
285
+ );
286
+
287
+ await expect(sdjwt.verify(encodedSdjwt)).rejects.toThrowError(
288
+ `Request to ${vct} timed out`,
289
+ );
290
+ });
291
+
292
+ test('VCT Metadata retrieval', async () => {
104
293
  const expectedPayload: SdJwtVcPayload = {
105
294
  iat,
106
295
  iss,
@@ -108,56 +297,351 @@ describe('App', () => {
108
297
  'vct#Integrity': vctIntegrity,
109
298
  ...claims,
110
299
  };
111
- const header = {
112
- vctm: [Buffer.from(JSON.stringify(exampleVctm)).toString('base64url')],
300
+ const encodedSdjwt = await sdjwt.issue(
301
+ expectedPayload,
302
+ disclosureFrame as unknown as DisclosureFrame<SdJwtVcPayload>,
303
+ );
304
+
305
+ const resolvedTypeMetadata = await sdjwt.getVct(encodedSdjwt);
306
+
307
+ // Check mergedTypeMetadata
308
+ expect(resolvedTypeMetadata?.mergedTypeMetadata).to.deep.eq({
309
+ description: 'An example credential type',
310
+ name: 'ExampleCredentialType',
311
+ vct: 'http://example.com/example',
312
+ });
313
+
314
+ // Check typeMetadataChain - should have only one document (no extends)
315
+ expect(resolvedTypeMetadata?.typeMetadataChain).toHaveLength(1);
316
+ expect(resolvedTypeMetadata?.typeMetadataChain[0].vct).toBe(
317
+ 'http://example.com/example',
318
+ );
319
+
320
+ // Check vctValues - should have only one value
321
+ expect(resolvedTypeMetadata?.vctValues).toHaveLength(1);
322
+ expect(resolvedTypeMetadata?.vctValues[0]).toBe(
323
+ 'http://example.com/example',
324
+ );
325
+ });
326
+
327
+ test('VCT with extends - simple chain', async () => {
328
+ const expectedPayload: SdJwtVcPayload = {
329
+ iat,
330
+ iss,
331
+ vct: 'http://example.com/extending',
332
+ ...claims,
113
333
  };
334
+
114
335
  const encodedSdjwt = await sdjwt.issue(
115
336
  expectedPayload,
116
337
  disclosureFrame as unknown as DisclosureFrame<SdJwtVcPayload>,
117
- { header },
118
338
  );
119
339
 
120
- await sdjwt.verify(encodedSdjwt);
340
+ const resolvedTypeMetadata = await sdjwt.getVct(encodedSdjwt);
341
+
342
+ // Check mergedTypeMetadata - should merge claims from both base and extending types
343
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.claims).toHaveLength(2);
344
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.claims?.[0].path).toEqual([
345
+ 'firstName',
346
+ ]);
347
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.claims?.[1].path).toEqual([
348
+ 'lastName',
349
+ ]);
350
+
351
+ // Display from extending type completely replaces base display (section 8.2)
352
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.display).toHaveLength(2);
353
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.display?.[0]).toEqual({
354
+ lang: 'en',
355
+ name: 'Extended Credential',
356
+ description: 'Extended description',
357
+ });
358
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.display?.[1]).toEqual({
359
+ lang: 'de',
360
+ name: 'Erweiterte Berechtigung',
361
+ description: 'Erweiterte Beschreibung',
362
+ });
363
+
364
+ // Top-level properties should come from extending type
365
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.name).toBe(
366
+ 'ExtendingCredentialType',
367
+ );
368
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.description).toBe(
369
+ 'A credential type that extends the base',
370
+ );
371
+
372
+ // Check typeMetadataChain - should have 2 documents in chain
373
+ expect(resolvedTypeMetadata?.typeMetadataChain).toHaveLength(2);
374
+ expect(resolvedTypeMetadata?.typeMetadataChain[0].vct).toBe(
375
+ 'http://example.com/extending',
376
+ );
377
+ expect(resolvedTypeMetadata?.typeMetadataChain[1].vct).toBe(
378
+ 'http://example.com/base',
379
+ );
380
+
381
+ // Check vctValues - should have 2 values
382
+ expect(resolvedTypeMetadata?.vctValues).toHaveLength(2);
383
+ expect(resolvedTypeMetadata?.vctValues[0]).toBe(
384
+ 'http://example.com/extending',
385
+ );
386
+ expect(resolvedTypeMetadata?.vctValues[1]).toBe('http://example.com/base');
121
387
  });
122
388
 
123
- test('VCT Validation with timeout', async () => {
124
- const vct = 'http://example.com/timeout';
389
+ test('VCT with extends - multi-level chain', async () => {
125
390
  const expectedPayload: SdJwtVcPayload = {
126
391
  iat,
127
392
  iss,
128
- vct,
393
+ vct: 'http://example.com/middle',
129
394
  ...claims,
130
395
  };
396
+
131
397
  const encodedSdjwt = await sdjwt.issue(
132
398
  expectedPayload,
133
399
  disclosureFrame as unknown as DisclosureFrame<SdJwtVcPayload>,
134
400
  );
135
401
 
136
- expect(sdjwt.verify(encodedSdjwt)).rejects.toThrowError(
137
- `Request to ${vct} timed out`,
402
+ const resolvedTypeMetadata = await sdjwt.getVct(encodedSdjwt);
403
+
404
+ // Check mergedTypeMetadata - should merge claims from base -> extending -> middle
405
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.claims).toHaveLength(3);
406
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.claims?.[0].path).toEqual([
407
+ 'firstName',
408
+ ]);
409
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.claims?.[1].path).toEqual([
410
+ 'lastName',
411
+ ]);
412
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.claims?.[2].path).toEqual([
413
+ 'age',
414
+ ]);
415
+
416
+ // Top-level properties should come from the most derived type
417
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.name).toBe(
418
+ 'MiddleCredentialType',
419
+ );
420
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.description).toBe(
421
+ 'Middle type in chain',
422
+ );
423
+
424
+ // Check typeMetadataChain - should have 3 documents in chain
425
+ expect(resolvedTypeMetadata?.typeMetadataChain).toHaveLength(3);
426
+ expect(resolvedTypeMetadata?.typeMetadataChain[0].vct).toBe(
427
+ 'http://example.com/middle',
138
428
  );
429
+ expect(resolvedTypeMetadata?.typeMetadataChain[1].vct).toBe(
430
+ 'http://example.com/extending',
431
+ );
432
+ expect(resolvedTypeMetadata?.typeMetadataChain[2].vct).toBe(
433
+ 'http://example.com/base',
434
+ );
435
+
436
+ // Check vctValues - should have 3 values
437
+ expect(resolvedTypeMetadata?.vctValues).toHaveLength(3);
438
+ expect(resolvedTypeMetadata?.vctValues[0]).toBe(
439
+ 'http://example.com/middle',
440
+ );
441
+ expect(resolvedTypeMetadata?.vctValues[1]).toBe(
442
+ 'http://example.com/extending',
443
+ );
444
+ expect(resolvedTypeMetadata?.vctValues[2]).toBe('http://example.com/base');
139
445
  });
140
446
 
141
- test('VCT Metadata retrieval', async () => {
447
+ test('VCT with circular dependency should throw error', async () => {
142
448
  const expectedPayload: SdJwtVcPayload = {
143
449
  iat,
144
450
  iss,
145
- vct,
146
- 'vct#Integrity': vctIntegrity,
451
+ vct: 'http://example.com/circular',
147
452
  ...claims,
148
453
  };
454
+
149
455
  const encodedSdjwt = await sdjwt.issue(
150
456
  expectedPayload,
151
457
  disclosureFrame as unknown as DisclosureFrame<SdJwtVcPayload>,
152
458
  );
153
459
 
154
- const typeMetadataFormat = await sdjwt.getVct(encodedSdjwt);
155
- expect(typeMetadataFormat).to.deep.eq({
156
- description: 'An example credential type',
157
- name: 'ExampleCredentialType',
158
- vct: 'http://example.com/example',
460
+ await expect(sdjwt.getVct(encodedSdjwt)).rejects.toThrowError(
461
+ 'Circular dependency detected in VCT extends chain: http://example.com/circular',
462
+ );
463
+ });
464
+
465
+ test('VCT with max depth exceeded should throw error', async () => {
466
+ const sdjwtWithShallowDepth = new SDJwtVcInstance({
467
+ signer,
468
+ signAlg: 'EdDSA',
469
+ verifier,
470
+ hasher: digest,
471
+ hashAlg: 'sha-256',
472
+ saltGenerator: generateSalt,
473
+ loadTypeMetadataFormat: true,
474
+ timeout: 1000,
475
+ maxVctExtendsDepth: 1, // Only allow 1 level of extends
159
476
  });
477
+
478
+ const expectedPayload: SdJwtVcPayload = {
479
+ iat,
480
+ iss,
481
+ vct: 'http://example.com/middle', // This has 2 levels of extends
482
+ ...claims,
483
+ };
484
+
485
+ const encodedSdjwt = await sdjwtWithShallowDepth.issue(
486
+ expectedPayload,
487
+ disclosureFrame as unknown as DisclosureFrame<SdJwtVcPayload>,
488
+ );
489
+
490
+ await expect(
491
+ sdjwtWithShallowDepth.getVct(encodedSdjwt),
492
+ ).rejects.toThrowError('Maximum VCT extends depth of 1 exceeded');
493
+ });
494
+
495
+ test('VCT extends chain should work in verify method', async () => {
496
+ const expectedPayload: SdJwtVcPayload = {
497
+ iat,
498
+ iss,
499
+ vct: 'http://example.com/extending',
500
+ ...claims,
501
+ };
502
+
503
+ const encodedSdjwt = await sdjwt.issue(
504
+ expectedPayload,
505
+ disclosureFrame as unknown as DisclosureFrame<SdJwtVcPayload>,
506
+ );
507
+
508
+ // Should not throw and should resolve the extends chain
509
+ const result = await sdjwt.verify(encodedSdjwt);
510
+ expect(result.payload.vct).toBe('http://example.com/extending');
511
+
512
+ // Check that typeMetadata was populated with resolved chain
513
+ expect(result.typeMetadata?.mergedTypeMetadata.claims).toHaveLength(2);
514
+ expect(result.typeMetadata?.typeMetadataChain).toHaveLength(2);
515
+ expect(result.typeMetadata?.vctValues).toHaveLength(2);
516
+ });
517
+
518
+ test('VCT with overriding claim metadata', async () => {
519
+ const expectedPayload: SdJwtVcPayload = {
520
+ iat,
521
+ iss,
522
+ vct: 'http://example.com/overriding',
523
+ ...claims,
524
+ };
525
+
526
+ const encodedSdjwt = await sdjwt.issue(
527
+ expectedPayload,
528
+ disclosureFrame as unknown as DisclosureFrame<SdJwtVcPayload>,
529
+ );
530
+
531
+ const resolvedTypeMetadata = await sdjwt.getVct(encodedSdjwt);
532
+
533
+ // Check mergedTypeMetadata - should have 2 claims: overridden firstName and new middleName
534
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.claims).toHaveLength(2);
535
+
536
+ // First claim should be the overridden firstName with new label and sd property
537
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.claims?.[0].path).toEqual([
538
+ 'firstName',
539
+ ]);
540
+ expect(
541
+ resolvedTypeMetadata?.mergedTypeMetadata.claims?.[0].display?.[0].label,
542
+ ).toBe('Given Name');
543
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.claims?.[0].sd).toBe(
544
+ 'always',
545
+ );
546
+
547
+ // Second claim should be the new middleName
548
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.claims?.[1].path).toEqual([
549
+ 'middleName',
550
+ ]);
551
+ expect(
552
+ resolvedTypeMetadata?.mergedTypeMetadata.claims?.[1].display?.[0].label,
553
+ ).toBe('Middle Name');
554
+
555
+ // Check typeMetadataChain - should have 2 documents
556
+ expect(resolvedTypeMetadata?.typeMetadataChain).toHaveLength(2);
557
+ expect(resolvedTypeMetadata?.typeMetadataChain[0].vct).toBe(
558
+ 'http://example.com/overriding',
559
+ );
560
+ expect(resolvedTypeMetadata?.typeMetadataChain[1].vct).toBe(
561
+ 'http://example.com/base',
562
+ );
563
+
564
+ // Check vctValues
565
+ expect(resolvedTypeMetadata?.vctValues).toHaveLength(2);
566
+ expect(resolvedTypeMetadata?.vctValues[0]).toBe(
567
+ 'http://example.com/overriding',
568
+ );
569
+ expect(resolvedTypeMetadata?.vctValues[1]).toBe('http://example.com/base');
570
+ });
571
+
572
+ test('VCT with valid sd property change (allowed to always)', async () => {
573
+ const expectedPayload: SdJwtVcPayload = {
574
+ iat,
575
+ iss,
576
+ vct: 'http://example.com/valid-sd-change',
577
+ ...claims,
578
+ };
579
+
580
+ const encodedSdjwt = await sdjwt.issue(
581
+ expectedPayload,
582
+ disclosureFrame as unknown as DisclosureFrame<SdJwtVcPayload>,
583
+ );
584
+
585
+ const resolvedTypeMetadata = await sdjwt.getVct(encodedSdjwt);
586
+
587
+ // Check mergedTypeMetadata - should successfully merge - changing from undefined/allowed to always is valid
588
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.claims).toHaveLength(1);
589
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.claims?.[0].path).toEqual([
590
+ 'firstName',
591
+ ]);
592
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.claims?.[0].sd).toBe(
593
+ 'always',
594
+ );
595
+
596
+ // Check typeMetadataChain
597
+ expect(resolvedTypeMetadata?.typeMetadataChain).toHaveLength(2);
598
+ expect(resolvedTypeMetadata?.vctValues).toHaveLength(2);
160
599
  });
161
600
 
162
- //TODO: we need tests with an embedded schema, extended and maybe also to test the errors when schema information is not available or the integrity is not valid
601
+ test('VCT with invalid sd property change (always to never) should throw error', async () => {
602
+ const expectedPayload: SdJwtVcPayload = {
603
+ iat,
604
+ iss,
605
+ vct: 'http://example.com/invalid-sd-change',
606
+ ...claims,
607
+ };
608
+
609
+ const encodedSdjwt = await sdjwt.issue(
610
+ expectedPayload,
611
+ disclosureFrame as unknown as DisclosureFrame<SdJwtVcPayload>,
612
+ );
613
+
614
+ await expect(sdjwt.getVct(encodedSdjwt)).rejects.toThrowError(
615
+ "Cannot change 'sd' property from 'always' to 'never' for claim at path [\"sensitiveData\"]",
616
+ );
617
+ });
618
+
619
+ test('VCT extending type without display should inherit base display', async () => {
620
+ const expectedPayload: SdJwtVcPayload = {
621
+ iat,
622
+ iss,
623
+ vct: 'http://example.com/middle', // middle doesn't define display
624
+ ...claims,
625
+ };
626
+
627
+ const encodedSdjwt = await sdjwt.issue(
628
+ expectedPayload,
629
+ disclosureFrame as unknown as DisclosureFrame<SdJwtVcPayload>,
630
+ );
631
+
632
+ const resolvedTypeMetadata = await sdjwt.getVct(encodedSdjwt);
633
+
634
+ // Check mergedTypeMetadata - since middle doesn't define display, it should inherit from extending which has display
635
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.display).toHaveLength(2);
636
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.display?.[0].lang).toBe(
637
+ 'en',
638
+ );
639
+ expect(resolvedTypeMetadata?.mergedTypeMetadata.display?.[1].lang).toBe(
640
+ 'de',
641
+ );
642
+
643
+ // Check typeMetadataChain - should have 3 documents
644
+ expect(resolvedTypeMetadata?.typeMetadataChain).toHaveLength(3);
645
+ expect(resolvedTypeMetadata?.vctValues).toHaveLength(3);
646
+ });
163
647
  });
@@ -1,6 +1,6 @@
1
1
  import type { kbHeader, kbPayload } from '@sd-jwt/types';
2
2
  import type { SdJwtVcPayload } from './sd-jwt-vc-payload';
3
- import type { TypeMetadataFormat } from './sd-jwt-vc-type-metadata-format';
3
+ import type { ResolvedTypeMetadata } from './sd-jwt-vc-type-metadata-format';
4
4
 
5
5
  export type VerificationResult = {
6
6
  payload: SdJwtVcPayload;
@@ -11,5 +11,6 @@ export type VerificationResult = {
11
11
  header: kbHeader;
12
12
  }
13
13
  | undefined;
14
- typeMetadataFormat?: TypeMetadataFormat;
14
+
15
+ typeMetadata?: ResolvedTypeMetadata;
15
16
  };
@@ -145,7 +145,9 @@ describe('App', () => {
145
145
  });
146
146
 
147
147
  const requiredClaimKeys = ['firstname', 'id', 'data.ssn'];
148
- const verified = await sdjwt.verify(encodedSdjwt, requiredClaimKeys);
148
+ const verified = await sdjwt.verify(encodedSdjwt, {
149
+ requiredClaimKeys,
150
+ });
149
151
  expect(verified).toBeDefined();
150
152
  });
151
153
 
@@ -239,7 +241,7 @@ async function JSONtest(filename: string) {
239
241
  payload,
240
242
  });
241
243
 
242
- const presentedSDJwt = await sdjwt.present<typeof claims>(
244
+ const presentedSDJwt = await sdjwt.present(
243
245
  encodedSdjwt,
244
246
  test.presentationFrames,
245
247
  );
@@ -249,13 +251,15 @@ async function JSONtest(filename: string) {
249
251
  const presentationClaims = await sdjwt.getClaims(presentedSDJwt);
250
252
 
251
253
  expect(presentationClaims).toEqual({
252
- ...test.presenatedClaims,
254
+ ...test.presentedClaims,
253
255
  iat,
254
256
  iss,
255
257
  vct,
256
258
  });
257
259
 
258
- const verified = await sdjwt.verify(encodedSdjwt, test.requiredClaimKeys);
260
+ const verified = await sdjwt.verify(encodedSdjwt, {
261
+ requiredClaimKeys: test.requiredClaimKeys,
262
+ });
259
263
 
260
264
  expect(verified).toBeDefined();
261
265
  expect(verified).toStrictEqual({
@@ -267,9 +271,11 @@ async function JSONtest(filename: string) {
267
271
 
268
272
  type TestJson = {
269
273
  claims: object;
270
- disclosureFrame: DisclosureFrame<object>;
271
- presentationFrames: PresentationFrame<object>;
272
- presenatedClaims: object;
274
+ // biome-ignore lint/complexity/noBannedTypes: we want an empty object in this case
275
+ disclosureFrame: DisclosureFrame<{}>;
276
+ // biome-ignore lint/complexity/noBannedTypes: we want an empty object in this case
277
+ presentationFrames: PresentationFrame<{}>;
278
+ presentedClaims: object;
273
279
  requiredClaimKeys: string[];
274
280
  };
275
281
 
@@ -17,7 +17,7 @@
17
17
  "5": true
18
18
  }
19
19
  },
20
- "presenatedClaims": {
20
+ "presentedClaims": {
21
21
  "data_types": [null, 42, 3.14, "foo", ["Test"], { "foo": "bar" }]
22
22
  },
23
23
  "requiredClaimKeys": [
@@ -12,7 +12,7 @@
12
12
  }
13
13
  },
14
14
  "presentationFrames": { "is_over": { "18": true } },
15
- "presenatedClaims": {
15
+ "presentedClaims": {
16
16
  "is_over": {
17
17
  "18": false
18
18
  }