@sd-jwt/core 0.3.2-next.75 → 0.3.2-next.94

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.
package/README.md CHANGED
@@ -32,44 +32,7 @@ Ensure you have Node.js installed as a prerequisite.
32
32
 
33
33
  ### Usage
34
34
 
35
- Here's a basic example of how to use this library:
36
-
37
- ```jsx
38
- import { DisclosureFrame } from '@sd-jwt/core';
39
-
40
- // Issuer defines the claims object with the user's information
41
- const claims = {
42
- firstname: 'John',
43
- lastname: 'Doe',
44
- ssn: '123-45-6789',
45
- id: '1234',
46
- };
47
-
48
- // Issuer defines the disclosure frame to specify which claims can be disclosed/undisclosed
49
- const disclosureFrame: DisclosureFrame<typeof claims> = {
50
- _sd: ['firstname', 'lastname', 'ssn'],
51
- };
52
-
53
- // Issuer issues a signed JWT credential with the specified claims and disclosure frame
54
- // returns an encoded JWT
55
- const credential = await sdjwt.issue(claims, disclosureFrame);
56
-
57
- // Holder may validate the credential from the issuer
58
- const valid = await sdjwt.validate(credential);
59
-
60
- // Holder defines the presentation frame to specify which claims should be presented
61
- // The list of presented claims must be a subset of the disclosed claims
62
- const presentationFrame = ['firstname', 'ssn'];
63
-
64
- // Holder creates a presentation using the issued credential and the presentation frame
65
- // returns an encoded SD JWT.
66
- const presentation = await sdjwt.present(credential, presentationFrame);
67
-
68
- // Verifier can verify the presentation using the Issuer's public key
69
- const verified = await sdjwt.verify(presentation);
70
- ```
71
-
72
- Check out more details in our [documentation](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/next/docs) or [examples](https://github.com/openwallet-foundation-labs/sd-jwt-js/tree/next/examples)
35
+ This library can not be used on it's own, it is a dependency for other implementations like `@sd-jwt/sd-jwt-vc`.
73
36
 
74
37
  ### Dependencies
75
38
 
package/dist/index.d.mts CHANGED
@@ -66,16 +66,19 @@ declare const pack: <T extends Record<string, unknown>>(claims: T, disclosureFra
66
66
 
67
67
  declare const createDecoy: (hash: HasherAndAlg, saltGenerator: SaltGenerator) => Promise<string>;
68
68
 
69
- declare class SDJwtInstance {
69
+ type SdJwtPayload = Record<string, unknown>;
70
+ declare abstract class SDJwtInstance<ExtendedPayload extends SdJwtPayload> {
71
+ protected abstract type: string;
70
72
  static DEFAULT_hashAlg: string;
71
73
  private userConfig;
72
74
  constructor(userConfig?: SDJWTConfig);
73
75
  private createKBJwt;
74
76
  private SignJwt;
75
77
  private VerifyJwt;
76
- issue<Payload extends Record<string, unknown>>(payload: Payload, disclosureFrame?: DisclosureFrame<Payload>, options?: {
78
+ issue<Payload extends ExtendedPayload>(payload: Payload, disclosureFrame?: DisclosureFrame<Payload>, options?: {
77
79
  header?: object;
78
80
  }): Promise<SDJWTCompact>;
81
+ protected abstract validateReservedFields<T extends ExtendedPayload>(disclosureFrame: DisclosureFrame<T>): void;
79
82
  present(encodedSDJwt: string, presentationKeys?: string[], options?: {
80
83
  kb?: KBOptions;
81
84
  }): Promise<SDJWTCompact>;
@@ -91,6 +94,7 @@ declare class SDJwtInstance {
91
94
  header: _sd_jwt_types.kbHeader;
92
95
  };
93
96
  }>;
97
+ private calculateSDHash;
94
98
  validate(encodedSDJwt: string): Promise<{
95
99
  payload: unknown;
96
100
  header: Record<string, unknown>;
@@ -103,4 +107,4 @@ declare class SDJwtInstance {
103
107
  getClaims(endcodedSDJwt: SDJWTCompact): Promise<unknown>;
104
108
  }
105
109
 
106
- export { Jwt, type JwtData, KBJwt, SDJwt, type SDJwtData, SDJwtInstance, createDecoy, listKeys, pack };
110
+ export { Jwt, type JwtData, KBJwt, SDJwt, type SDJwtData, SDJwtInstance, type SdJwtPayload, createDecoy, listKeys, pack };
package/dist/index.d.ts CHANGED
@@ -66,16 +66,19 @@ declare const pack: <T extends Record<string, unknown>>(claims: T, disclosureFra
66
66
 
67
67
  declare const createDecoy: (hash: HasherAndAlg, saltGenerator: SaltGenerator) => Promise<string>;
68
68
 
69
- declare class SDJwtInstance {
69
+ type SdJwtPayload = Record<string, unknown>;
70
+ declare abstract class SDJwtInstance<ExtendedPayload extends SdJwtPayload> {
71
+ protected abstract type: string;
70
72
  static DEFAULT_hashAlg: string;
71
73
  private userConfig;
72
74
  constructor(userConfig?: SDJWTConfig);
73
75
  private createKBJwt;
74
76
  private SignJwt;
75
77
  private VerifyJwt;
76
- issue<Payload extends Record<string, unknown>>(payload: Payload, disclosureFrame?: DisclosureFrame<Payload>, options?: {
78
+ issue<Payload extends ExtendedPayload>(payload: Payload, disclosureFrame?: DisclosureFrame<Payload>, options?: {
77
79
  header?: object;
78
80
  }): Promise<SDJWTCompact>;
81
+ protected abstract validateReservedFields<T extends ExtendedPayload>(disclosureFrame: DisclosureFrame<T>): void;
79
82
  present(encodedSDJwt: string, presentationKeys?: string[], options?: {
80
83
  kb?: KBOptions;
81
84
  }): Promise<SDJWTCompact>;
@@ -91,6 +94,7 @@ declare class SDJwtInstance {
91
94
  header: _sd_jwt_types.kbHeader;
92
95
  };
93
96
  }>;
97
+ private calculateSDHash;
94
98
  validate(encodedSDJwt: string): Promise<{
95
99
  payload: unknown;
96
100
  header: Record<string, unknown>;
@@ -103,4 +107,4 @@ declare class SDJwtInstance {
103
107
  getClaims(endcodedSDJwt: SDJWTCompact): Promise<unknown>;
104
108
  }
105
109
 
106
- export { Jwt, type JwtData, KBJwt, SDJwt, type SDJwtData, SDJwtInstance, createDecoy, listKeys, pack };
110
+ export { Jwt, type JwtData, KBJwt, SDJwt, type SDJwtData, SDJwtInstance, type SdJwtPayload, createDecoy, listKeys, pack };
package/dist/index.js CHANGED
@@ -403,6 +403,7 @@ var pack = (claims, disclosureFrame, hash, saltGenerator) => __async(void 0, nul
403
403
 
404
404
  // src/index.ts
405
405
  var import_types2 = require("@sd-jwt/types");
406
+ var import_decode3 = require("@sd-jwt/decode");
406
407
  var _SDJwtInstance = class _SDJwtInstance {
407
408
  constructor(userConfig) {
408
409
  this.userConfig = {};
@@ -410,7 +411,7 @@ var _SDJwtInstance = class _SDJwtInstance {
410
411
  this.userConfig = userConfig;
411
412
  }
412
413
  }
413
- createKBJwt(options) {
414
+ createKBJwt(options, sdHash) {
414
415
  return __async(this, null, function* () {
415
416
  if (!this.userConfig.kbSigner) {
416
417
  throw new import_utils5.SDJWTException("Key Binding Signer not found");
@@ -424,7 +425,7 @@ var _SDJwtInstance = class _SDJwtInstance {
424
425
  typ: import_types2.KB_JWT_TYP,
425
426
  alg: this.userConfig.kbSignAlg
426
427
  },
427
- payload
428
+ payload: __spreadProps(__spreadValues({}, payload), { sd_hash: sdHash })
428
429
  });
429
430
  yield kbJwt.sign(this.userConfig.kbSigner);
430
431
  return kbJwt;
@@ -459,6 +460,9 @@ var _SDJwtInstance = class _SDJwtInstance {
459
460
  if (!this.userConfig.signAlg) {
460
461
  throw new import_utils5.SDJWTException("sign alogrithm not specified");
461
462
  }
463
+ if (disclosureFrame) {
464
+ this.validateReservedFields(disclosureFrame);
465
+ }
462
466
  const hasher = this.userConfig.hasher;
463
467
  const hashAlg = (_a = this.userConfig.hashAlg) != null ? _a : _SDJwtInstance.DEFAULT_hashAlg;
464
468
  const { packedClaims, disclosures } = yield pack(
@@ -469,7 +473,7 @@ var _SDJwtInstance = class _SDJwtInstance {
469
473
  );
470
474
  const alg = this.userConfig.signAlg;
471
475
  const OptionHeader = (_b = options == null ? void 0 : options.header) != null ? _b : {};
472
- const CustomHeader = this.userConfig.omitTyp ? OptionHeader : __spreadValues({ typ: import_types2.SD_JWT_TYP }, OptionHeader);
476
+ const CustomHeader = this.userConfig.omitTyp ? OptionHeader : __spreadValues({ typ: this.type }, OptionHeader);
473
477
  const header = __spreadProps(__spreadValues({}, CustomHeader), { alg });
474
478
  const jwt = new Jwt({
475
479
  header,
@@ -487,6 +491,7 @@ var _SDJwtInstance = class _SDJwtInstance {
487
491
  }
488
492
  present(encodedSDJwt, presentationKeys, options) {
489
493
  return __async(this, null, function* () {
494
+ var _a;
490
495
  if (!presentationKeys)
491
496
  return encodedSDJwt;
492
497
  if (!this.userConfig.hasher) {
@@ -494,8 +499,21 @@ var _SDJwtInstance = class _SDJwtInstance {
494
499
  }
495
500
  const hasher = this.userConfig.hasher;
496
501
  const sdjwt = yield SDJwt.fromEncode(encodedSDJwt, hasher);
497
- const kbJwt = (options == null ? void 0 : options.kb) ? yield this.createKBJwt(options.kb) : void 0;
498
- sdjwt.kbJwt = kbJwt;
502
+ if (!((_a = sdjwt.jwt) == null ? void 0 : _a.payload))
503
+ throw new import_utils5.SDJWTException("Payload not found");
504
+ const presentSdJwtWithoutKb = yield sdjwt.present(
505
+ presentationKeys.sort(),
506
+ hasher
507
+ );
508
+ if (!(options == null ? void 0 : options.kb)) {
509
+ return presentSdJwtWithoutKb;
510
+ }
511
+ const sdHashStr = yield this.calculateSDHash(
512
+ presentSdJwtWithoutKb,
513
+ sdjwt,
514
+ hasher
515
+ );
516
+ sdjwt.kbJwt = yield this.createKBJwt(options.kb, sdHashStr);
499
517
  return sdjwt.present(presentationKeys.sort(), hasher);
500
518
  });
501
519
  }
@@ -509,7 +527,7 @@ var _SDJwtInstance = class _SDJwtInstance {
509
527
  }
510
528
  const hasher = this.userConfig.hasher;
511
529
  const sdjwt = yield SDJwt.fromEncode(encodedSDJwt, hasher);
512
- if (!sdjwt.jwt) {
530
+ if (!sdjwt.jwt || !sdjwt.jwt.payload) {
513
531
  throw new import_utils5.SDJWTException("Invalid SD JWT");
514
532
  }
515
533
  const { payload, header } = yield this.validate(encodedSDJwt);
@@ -532,9 +550,34 @@ var _SDJwtInstance = class _SDJwtInstance {
532
550
  throw new import_utils5.SDJWTException("Key Binding Verifier not found");
533
551
  }
534
552
  const kb = yield sdjwt.kbJwt.verify(this.userConfig.kbVerifier);
553
+ const sdHashfromKb = kb.payload.sd_hash;
554
+ const sdjwtWithoutKb = new SDJwt({
555
+ jwt: sdjwt.jwt,
556
+ disclosures: sdjwt.disclosures
557
+ });
558
+ const presentSdJwtWithoutKb = sdjwtWithoutKb.encodeSDJwt();
559
+ const sdHashStr = yield this.calculateSDHash(
560
+ presentSdJwtWithoutKb,
561
+ sdjwt,
562
+ hasher
563
+ );
564
+ if (sdHashStr !== sdHashfromKb) {
565
+ throw new import_utils5.SDJWTException("Invalid sd_hash in Key Binding JWT");
566
+ }
535
567
  return { payload, header, kb };
536
568
  });
537
569
  }
570
+ calculateSDHash(presentSdJwtWithoutKb, sdjwt, hasher) {
571
+ return __async(this, null, function* () {
572
+ if (!sdjwt.jwt || !sdjwt.jwt.payload) {
573
+ throw new import_utils5.SDJWTException("Invalid SD JWT");
574
+ }
575
+ const { _sd_alg } = (0, import_decode3.getSDAlgAndPayload)(sdjwt.jwt.payload);
576
+ const sdHash = yield hasher(presentSdJwtWithoutKb, _sd_alg);
577
+ const sdHashStr = (0, import_utils5.Uint8ArrayToBase64Url)(sdHash);
578
+ return sdHashStr;
579
+ });
580
+ }
538
581
  // This function is for validating the SD JWT
539
582
  // Just checking signature and return its the claims
540
583
  validate(encodedSDJwt) {
package/dist/index.mjs CHANGED
@@ -42,7 +42,7 @@ var __async = (__this, __arguments, generator) => {
42
42
  };
43
43
 
44
44
  // src/index.ts
45
- import { SDJWTException as SDJWTException4 } from "@sd-jwt/utils";
45
+ import { SDJWTException as SDJWTException4, Uint8ArrayToBase64Url as Uint8ArrayToBase64Url2 } from "@sd-jwt/utils";
46
46
 
47
47
  // src/jwt.ts
48
48
  import { Base64urlEncode, SDJWTException } from "@sd-jwt/utils";
@@ -381,9 +381,9 @@ var pack = (claims, disclosureFrame, hash, saltGenerator) => __async(void 0, nul
381
381
 
382
382
  // src/index.ts
383
383
  import {
384
- KB_JWT_TYP,
385
- SD_JWT_TYP
384
+ KB_JWT_TYP as KB_JWT_TYP2
386
385
  } from "@sd-jwt/types";
386
+ import { getSDAlgAndPayload as getSDAlgAndPayload2 } from "@sd-jwt/decode";
387
387
  var _SDJwtInstance = class _SDJwtInstance {
388
388
  constructor(userConfig) {
389
389
  this.userConfig = {};
@@ -391,7 +391,7 @@ var _SDJwtInstance = class _SDJwtInstance {
391
391
  this.userConfig = userConfig;
392
392
  }
393
393
  }
394
- createKBJwt(options) {
394
+ createKBJwt(options, sdHash) {
395
395
  return __async(this, null, function* () {
396
396
  if (!this.userConfig.kbSigner) {
397
397
  throw new SDJWTException4("Key Binding Signer not found");
@@ -402,10 +402,10 @@ var _SDJwtInstance = class _SDJwtInstance {
402
402
  const { payload } = options;
403
403
  const kbJwt = new KBJwt({
404
404
  header: {
405
- typ: KB_JWT_TYP,
405
+ typ: KB_JWT_TYP2,
406
406
  alg: this.userConfig.kbSignAlg
407
407
  },
408
- payload
408
+ payload: __spreadProps(__spreadValues({}, payload), { sd_hash: sdHash })
409
409
  });
410
410
  yield kbJwt.sign(this.userConfig.kbSigner);
411
411
  return kbJwt;
@@ -440,6 +440,9 @@ var _SDJwtInstance = class _SDJwtInstance {
440
440
  if (!this.userConfig.signAlg) {
441
441
  throw new SDJWTException4("sign alogrithm not specified");
442
442
  }
443
+ if (disclosureFrame) {
444
+ this.validateReservedFields(disclosureFrame);
445
+ }
443
446
  const hasher = this.userConfig.hasher;
444
447
  const hashAlg = (_a = this.userConfig.hashAlg) != null ? _a : _SDJwtInstance.DEFAULT_hashAlg;
445
448
  const { packedClaims, disclosures } = yield pack(
@@ -450,7 +453,7 @@ var _SDJwtInstance = class _SDJwtInstance {
450
453
  );
451
454
  const alg = this.userConfig.signAlg;
452
455
  const OptionHeader = (_b = options == null ? void 0 : options.header) != null ? _b : {};
453
- const CustomHeader = this.userConfig.omitTyp ? OptionHeader : __spreadValues({ typ: SD_JWT_TYP }, OptionHeader);
456
+ const CustomHeader = this.userConfig.omitTyp ? OptionHeader : __spreadValues({ typ: this.type }, OptionHeader);
454
457
  const header = __spreadProps(__spreadValues({}, CustomHeader), { alg });
455
458
  const jwt = new Jwt({
456
459
  header,
@@ -468,6 +471,7 @@ var _SDJwtInstance = class _SDJwtInstance {
468
471
  }
469
472
  present(encodedSDJwt, presentationKeys, options) {
470
473
  return __async(this, null, function* () {
474
+ var _a;
471
475
  if (!presentationKeys)
472
476
  return encodedSDJwt;
473
477
  if (!this.userConfig.hasher) {
@@ -475,8 +479,21 @@ var _SDJwtInstance = class _SDJwtInstance {
475
479
  }
476
480
  const hasher = this.userConfig.hasher;
477
481
  const sdjwt = yield SDJwt.fromEncode(encodedSDJwt, hasher);
478
- const kbJwt = (options == null ? void 0 : options.kb) ? yield this.createKBJwt(options.kb) : void 0;
479
- sdjwt.kbJwt = kbJwt;
482
+ if (!((_a = sdjwt.jwt) == null ? void 0 : _a.payload))
483
+ throw new SDJWTException4("Payload not found");
484
+ const presentSdJwtWithoutKb = yield sdjwt.present(
485
+ presentationKeys.sort(),
486
+ hasher
487
+ );
488
+ if (!(options == null ? void 0 : options.kb)) {
489
+ return presentSdJwtWithoutKb;
490
+ }
491
+ const sdHashStr = yield this.calculateSDHash(
492
+ presentSdJwtWithoutKb,
493
+ sdjwt,
494
+ hasher
495
+ );
496
+ sdjwt.kbJwt = yield this.createKBJwt(options.kb, sdHashStr);
480
497
  return sdjwt.present(presentationKeys.sort(), hasher);
481
498
  });
482
499
  }
@@ -490,7 +507,7 @@ var _SDJwtInstance = class _SDJwtInstance {
490
507
  }
491
508
  const hasher = this.userConfig.hasher;
492
509
  const sdjwt = yield SDJwt.fromEncode(encodedSDJwt, hasher);
493
- if (!sdjwt.jwt) {
510
+ if (!sdjwt.jwt || !sdjwt.jwt.payload) {
494
511
  throw new SDJWTException4("Invalid SD JWT");
495
512
  }
496
513
  const { payload, header } = yield this.validate(encodedSDJwt);
@@ -513,9 +530,34 @@ var _SDJwtInstance = class _SDJwtInstance {
513
530
  throw new SDJWTException4("Key Binding Verifier not found");
514
531
  }
515
532
  const kb = yield sdjwt.kbJwt.verify(this.userConfig.kbVerifier);
533
+ const sdHashfromKb = kb.payload.sd_hash;
534
+ const sdjwtWithoutKb = new SDJwt({
535
+ jwt: sdjwt.jwt,
536
+ disclosures: sdjwt.disclosures
537
+ });
538
+ const presentSdJwtWithoutKb = sdjwtWithoutKb.encodeSDJwt();
539
+ const sdHashStr = yield this.calculateSDHash(
540
+ presentSdJwtWithoutKb,
541
+ sdjwt,
542
+ hasher
543
+ );
544
+ if (sdHashStr !== sdHashfromKb) {
545
+ throw new SDJWTException4("Invalid sd_hash in Key Binding JWT");
546
+ }
516
547
  return { payload, header, kb };
517
548
  });
518
549
  }
550
+ calculateSDHash(presentSdJwtWithoutKb, sdjwt, hasher) {
551
+ return __async(this, null, function* () {
552
+ if (!sdjwt.jwt || !sdjwt.jwt.payload) {
553
+ throw new SDJWTException4("Invalid SD JWT");
554
+ }
555
+ const { _sd_alg } = getSDAlgAndPayload2(sdjwt.jwt.payload);
556
+ const sdHash = yield hasher(presentSdJwtWithoutKb, _sd_alg);
557
+ const sdHashStr = Uint8ArrayToBase64Url2(sdHash);
558
+ return sdHashStr;
559
+ });
560
+ }
519
561
  // This function is for validating the SD JWT
520
562
  // Just checking signature and return its the claims
521
563
  validate(encodedSDJwt) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sd-jwt/core",
3
- "version": "0.3.2-next.75+c1b701d",
3
+ "version": "0.3.2-next.94+32af6cf",
4
4
  "description": "sd-jwt draft 7 implementation in typescript",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -39,12 +39,12 @@
39
39
  },
40
40
  "license": "Apache-2.0",
41
41
  "devDependencies": {
42
- "@sd-jwt/crypto-nodejs": "0.3.2-next.75+c1b701d"
42
+ "@sd-jwt/crypto-nodejs": "0.3.2-next.94+32af6cf"
43
43
  },
44
44
  "dependencies": {
45
- "@sd-jwt/decode": "0.3.2-next.75+c1b701d",
46
- "@sd-jwt/types": "0.3.2-next.75+c1b701d",
47
- "@sd-jwt/utils": "0.3.2-next.75+c1b701d"
45
+ "@sd-jwt/decode": "0.3.2-next.94+32af6cf",
46
+ "@sd-jwt/types": "0.3.2-next.94+32af6cf",
47
+ "@sd-jwt/utils": "0.3.2-next.94+32af6cf"
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public"
@@ -62,5 +62,5 @@
62
62
  "esm"
63
63
  ]
64
64
  },
65
- "gitHead": "c1b701d6c1998c22dcc9efc0f91229177c7c8eea"
65
+ "gitHead": "32af6cfa150fceb440fc9225bcaf2791a6aeee90"
66
66
  }
package/src/index.ts CHANGED
@@ -1,22 +1,28 @@
1
- import { SDJWTException } from '@sd-jwt/utils';
1
+ import { SDJWTException, Uint8ArrayToBase64Url } from '@sd-jwt/utils';
2
2
  import { Jwt } from './jwt';
3
3
  import { KBJwt } from './kbjwt';
4
4
  import { SDJwt, pack } from './sdjwt';
5
5
  import {
6
6
  DisclosureFrame,
7
+ Hasher,
7
8
  KBOptions,
8
9
  KB_JWT_TYP,
9
10
  SDJWTCompact,
10
11
  SDJWTConfig,
11
- SD_JWT_TYP,
12
12
  } from '@sd-jwt/types';
13
+ import { getSDAlgAndPayload } from '@sd-jwt/decode';
13
14
 
14
15
  export * from './sdjwt';
15
16
  export * from './kbjwt';
16
17
  export * from './jwt';
17
18
  export * from './decoy';
18
19
 
19
- export class SDJwtInstance {
20
+ export type SdJwtPayload = Record<string, unknown>;
21
+
22
+ export abstract class SDJwtInstance<ExtendedPayload extends SdJwtPayload> {
23
+ //header type
24
+ protected abstract type: string;
25
+
20
26
  public static DEFAULT_hashAlg = 'sha-256';
21
27
 
22
28
  private userConfig: SDJWTConfig = {};
@@ -27,20 +33,24 @@ export class SDJwtInstance {
27
33
  }
28
34
  }
29
35
 
30
- private async createKBJwt(options: KBOptions): Promise<KBJwt> {
36
+ private async createKBJwt(
37
+ options: KBOptions,
38
+ sdHash: string,
39
+ ): Promise<KBJwt> {
31
40
  if (!this.userConfig.kbSigner) {
32
41
  throw new SDJWTException('Key Binding Signer not found');
33
42
  }
34
43
  if (!this.userConfig.kbSignAlg) {
35
44
  throw new SDJWTException('Key Binding sign algorithm not specified');
36
45
  }
46
+
37
47
  const { payload } = options;
38
48
  const kbJwt = new KBJwt({
39
49
  header: {
40
50
  typ: KB_JWT_TYP,
41
51
  alg: this.userConfig.kbSignAlg,
42
52
  },
43
- payload,
53
+ payload: { ...payload, sd_hash: sdHash },
44
54
  });
45
55
 
46
56
  await kbJwt.sign(this.userConfig.kbSigner);
@@ -62,7 +72,7 @@ export class SDJwtInstance {
62
72
  return jwt.verify(this.userConfig.verifier);
63
73
  }
64
74
 
65
- public async issue<Payload extends Record<string, unknown>>(
75
+ public async issue<Payload extends ExtendedPayload>(
66
76
  payload: Payload,
67
77
  disclosureFrame?: DisclosureFrame<Payload>,
68
78
  options?: {
@@ -81,6 +91,10 @@ export class SDJwtInstance {
81
91
  throw new SDJWTException('sign alogrithm not specified');
82
92
  }
83
93
 
94
+ if (disclosureFrame) {
95
+ this.validateReservedFields<Payload>(disclosureFrame);
96
+ }
97
+
84
98
  const hasher = this.userConfig.hasher;
85
99
  const hashAlg = this.userConfig.hashAlg ?? SDJwtInstance.DEFAULT_hashAlg;
86
100
 
@@ -94,7 +108,7 @@ export class SDJwtInstance {
94
108
  const OptionHeader = options?.header ?? {};
95
109
  const CustomHeader = this.userConfig.omitTyp
96
110
  ? OptionHeader
97
- : { typ: SD_JWT_TYP, ...OptionHeader };
111
+ : { typ: this.type, ...OptionHeader };
98
112
  const header = { ...CustomHeader, alg };
99
113
  const jwt = new Jwt({
100
114
  header,
@@ -113,6 +127,10 @@ export class SDJwtInstance {
113
127
  return sdJwt.encodeSDJwt();
114
128
  }
115
129
 
130
+ protected abstract validateReservedFields<T extends ExtendedPayload>(
131
+ disclosureFrame: DisclosureFrame<T>,
132
+ ): void;
133
+
116
134
  public async present(
117
135
  encodedSDJwt: string,
118
136
  presentationKeys?: string[],
@@ -127,9 +145,24 @@ export class SDJwtInstance {
127
145
  const hasher = this.userConfig.hasher;
128
146
 
129
147
  const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
130
- const kbJwt = options?.kb ? await this.createKBJwt(options.kb) : undefined;
131
- sdjwt.kbJwt = kbJwt;
132
148
 
149
+ if (!sdjwt.jwt?.payload) throw new SDJWTException('Payload not found');
150
+ const presentSdJwtWithoutKb = await sdjwt.present(
151
+ presentationKeys.sort(),
152
+ hasher,
153
+ );
154
+
155
+ if (!options?.kb) {
156
+ return presentSdJwtWithoutKb;
157
+ }
158
+
159
+ const sdHashStr = await this.calculateSDHash(
160
+ presentSdJwtWithoutKb,
161
+ sdjwt,
162
+ hasher,
163
+ );
164
+
165
+ sdjwt.kbJwt = await this.createKBJwt(options.kb, sdHashStr);
133
166
  return sdjwt.present(presentationKeys.sort(), hasher);
134
167
  }
135
168
 
@@ -147,7 +180,7 @@ export class SDJwtInstance {
147
180
  const hasher = this.userConfig.hasher;
148
181
 
149
182
  const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
150
- if (!sdjwt.jwt) {
183
+ if (!sdjwt.jwt || !sdjwt.jwt.payload) {
151
184
  throw new SDJWTException('Invalid SD JWT');
152
185
  }
153
186
  const { payload, header } = await this.validate(encodedSDJwt);
@@ -173,9 +206,40 @@ export class SDJwtInstance {
173
206
  throw new SDJWTException('Key Binding Verifier not found');
174
207
  }
175
208
  const kb = await sdjwt.kbJwt.verify(this.userConfig.kbVerifier);
209
+ const sdHashfromKb = kb.payload.sd_hash;
210
+ const sdjwtWithoutKb = new SDJwt({
211
+ jwt: sdjwt.jwt,
212
+ disclosures: sdjwt.disclosures,
213
+ });
214
+
215
+ const presentSdJwtWithoutKb = sdjwtWithoutKb.encodeSDJwt();
216
+ const sdHashStr = await this.calculateSDHash(
217
+ presentSdJwtWithoutKb,
218
+ sdjwt,
219
+ hasher,
220
+ );
221
+
222
+ if (sdHashStr !== sdHashfromKb) {
223
+ throw new SDJWTException('Invalid sd_hash in Key Binding JWT');
224
+ }
225
+
176
226
  return { payload, header, kb };
177
227
  }
178
228
 
229
+ private async calculateSDHash(
230
+ presentSdJwtWithoutKb: string,
231
+ sdjwt: SDJwt,
232
+ hasher: Hasher,
233
+ ) {
234
+ if (!sdjwt.jwt || !sdjwt.jwt.payload) {
235
+ throw new SDJWTException('Invalid SD JWT');
236
+ }
237
+ const { _sd_alg } = getSDAlgAndPayload(sdjwt.jwt.payload);
238
+ const sdHash = await hasher(presentSdJwtWithoutKb, _sd_alg);
239
+ const sdHashStr = Uint8ArrayToBase64Url(sdHash);
240
+ return sdHashStr;
241
+ }
242
+
179
243
  // This function is for validating the SD JWT
180
244
  // Just checking signature and return its the claims
181
245
  public async validate(encodedSDJwt: string) {
package/src/sdjwt.ts CHANGED
@@ -6,12 +6,15 @@ import {
6
6
  DisclosureFrame,
7
7
  Hasher,
8
8
  HasherAndAlg,
9
+ KBOptions,
10
+ KB_JWT_TYP,
9
11
  SDJWTCompact,
10
12
  SD_DECOY,
11
13
  SD_DIGEST,
12
14
  SD_LIST_KEY,
13
15
  SD_SEPARATOR,
14
16
  SaltGenerator,
17
+ Signer,
15
18
  kbHeader,
16
19
  kbPayload,
17
20
  } from '@sd-jwt/types';
@@ -1,9 +1,19 @@
1
- import { SDJwtInstance } from '../index';
2
- import { Signer, Verifier } from '@sd-jwt/types';
1
+ import { SDJwtInstance, SdJwtPayload } from '../index';
2
+ import { DisclosureFrame, Signer, Verifier } from '@sd-jwt/types';
3
3
  import Crypto from 'node:crypto';
4
4
  import { describe, expect, test } from 'vitest';
5
5
  import { digest, generateSalt } from '@sd-jwt/crypto-nodejs';
6
6
 
7
+ export class TestInstance extends SDJwtInstance<SdJwtPayload> {
8
+ protected type = 'sd-jwt';
9
+
10
+ protected validateReservedFields(
11
+ disclosureFrame: DisclosureFrame<SdJwtPayload>,
12
+ ): void {
13
+ return;
14
+ }
15
+ }
16
+
7
17
  export const createSignerVerifier = () => {
8
18
  const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
9
19
  const signer: Signer = async (data: string) => {
@@ -23,13 +33,13 @@ export const createSignerVerifier = () => {
23
33
 
24
34
  describe('index', () => {
25
35
  test('create', async () => {
26
- const sdjwt = new SDJwtInstance();
36
+ const sdjwt = new TestInstance();
27
37
  expect(sdjwt).toBeDefined();
28
38
  });
29
39
 
30
40
  test('kbJwt', async () => {
31
41
  const { signer, verifier } = createSignerVerifier();
32
- const sdjwt = new SDJwtInstance({
42
+ const sdjwt = new TestInstance({
33
43
  signer,
34
44
  signAlg: 'EdDSA',
35
45
  verifier,
@@ -41,6 +51,9 @@ describe('index', () => {
41
51
  const credential = await sdjwt.issue(
42
52
  {
43
53
  foo: 'bar',
54
+ iss: 'Issuer',
55
+ iat: new Date().getTime(),
56
+ vct: '',
44
57
  },
45
58
  {
46
59
  _sd: ['foo'],
@@ -52,7 +65,6 @@ describe('index', () => {
52
65
  const presentation = await sdjwt.present(credential, ['foo'], {
53
66
  kb: {
54
67
  payload: {
55
- sd_hash: 'sha-256',
56
68
  aud: '1',
57
69
  iat: 1,
58
70
  nonce: '342',
@@ -65,7 +77,7 @@ describe('index', () => {
65
77
 
66
78
  test('issue', async () => {
67
79
  const { signer, verifier } = createSignerVerifier();
68
- const sdjwt = new SDJwtInstance({
80
+ const sdjwt = new TestInstance({
69
81
  signer,
70
82
  signAlg: 'EdDSA',
71
83
  verifier,
@@ -75,6 +87,9 @@ describe('index', () => {
75
87
  const credential = await sdjwt.issue(
76
88
  {
77
89
  foo: 'bar',
90
+ iss: 'Issuer',
91
+ iat: new Date().getTime(),
92
+ vct: '',
78
93
  },
79
94
  {
80
95
  _sd: ['foo'],
@@ -96,7 +111,7 @@ describe('index', () => {
96
111
  );
97
112
  };
98
113
 
99
- const sdjwt = new SDJwtInstance({
114
+ const sdjwt = new TestInstance({
100
115
  signer,
101
116
  signAlg: 'EdDSA',
102
117
  verifier: failedverifier,
@@ -107,6 +122,9 @@ describe('index', () => {
107
122
  const credential = await sdjwt.issue(
108
123
  {
109
124
  foo: 'bar',
125
+ iss: 'Issuer',
126
+ iat: new Date().getTime(),
127
+ vct: '',
110
128
  },
111
129
  {
112
130
  _sd: ['foo'],
@@ -131,7 +149,7 @@ describe('index', () => {
131
149
  Buffer.from(sig, 'base64url'),
132
150
  );
133
151
  };
134
- const sdjwt = new SDJwtInstance({
152
+ const sdjwt = new TestInstance({
135
153
  signer,
136
154
  signAlg: 'EdDSA',
137
155
  verifier,
@@ -145,6 +163,9 @@ describe('index', () => {
145
163
  const credential = await sdjwt.issue(
146
164
  {
147
165
  foo: 'bar',
166
+ iss: 'Issuer',
167
+ iat: new Date().getTime(),
168
+ vct: '',
148
169
  },
149
170
  {
150
171
  _sd: ['foo'],
@@ -154,8 +175,7 @@ describe('index', () => {
154
175
  const presentation = await sdjwt.present(credential, ['foo'], {
155
176
  kb: {
156
177
  payload: {
157
- sd_hash: '',
158
- aud: '1',
178
+ aud: '',
159
179
  iat: 1,
160
180
  nonce: '342',
161
181
  },
@@ -171,7 +191,7 @@ describe('index', () => {
171
191
 
172
192
  test('verify with kbJwt', async () => {
173
193
  const { signer, verifier } = createSignerVerifier();
174
- const sdjwt = new SDJwtInstance({
194
+ const sdjwt = new TestInstance({
175
195
  signer,
176
196
  signAlg: 'EdDSA',
177
197
  verifier,
@@ -185,6 +205,9 @@ describe('index', () => {
185
205
  const credential = await sdjwt.issue(
186
206
  {
187
207
  foo: 'bar',
208
+ iss: 'Issuer',
209
+ iat: new Date().getTime(),
210
+ vct: '',
188
211
  },
189
212
  {
190
213
  _sd: ['foo'],
@@ -194,7 +217,6 @@ describe('index', () => {
194
217
  const presentation = await sdjwt.present(credential, ['foo'], {
195
218
  kb: {
196
219
  payload: {
197
- sd_hash: 'sha-256',
198
220
  aud: '1',
199
221
  iat: 1,
200
222
  nonce: '342',
@@ -207,11 +229,14 @@ describe('index', () => {
207
229
  });
208
230
 
209
231
  test('Hasher not found', async () => {
210
- const sdjwt = new SDJwtInstance({});
232
+ const sdjwt = new TestInstance({});
211
233
  try {
212
234
  const credential = await sdjwt.issue(
213
235
  {
214
236
  foo: 'bar',
237
+ iss: 'Issuer',
238
+ iat: new Date().getTime(),
239
+ vct: '',
215
240
  },
216
241
  {
217
242
  _sd: ['foo'],
@@ -225,13 +250,16 @@ describe('index', () => {
225
250
  });
226
251
 
227
252
  test('SaltGenerator not found', async () => {
228
- const sdjwt = new SDJwtInstance({
253
+ const sdjwt = new TestInstance({
229
254
  hasher: digest,
230
255
  });
231
256
  try {
232
257
  const credential = await sdjwt.issue(
233
258
  {
234
259
  foo: 'bar',
260
+ iss: 'Issuer',
261
+ iat: new Date().getTime(),
262
+ vct: '',
235
263
  },
236
264
  {
237
265
  _sd: ['foo'],
@@ -245,7 +273,7 @@ describe('index', () => {
245
273
  });
246
274
 
247
275
  test('Signer not found', async () => {
248
- const sdjwt = new SDJwtInstance({
276
+ const sdjwt = new TestInstance({
249
277
  hasher: digest,
250
278
  saltGenerator: generateSalt,
251
279
  });
@@ -253,6 +281,9 @@ describe('index', () => {
253
281
  const credential = await sdjwt.issue(
254
282
  {
255
283
  foo: 'bar',
284
+ iss: 'Issuer',
285
+ iat: new Date().getTime(),
286
+ vct: '',
256
287
  },
257
288
  {
258
289
  _sd: ['foo'],
@@ -267,7 +298,7 @@ describe('index', () => {
267
298
 
268
299
  test('Verifier not found', async () => {
269
300
  const { signer, verifier } = createSignerVerifier();
270
- const sdjwt = new SDJwtInstance({
301
+ const sdjwt = new TestInstance({
271
302
  signer,
272
303
  hasher: digest,
273
304
  saltGenerator: generateSalt,
@@ -280,6 +311,9 @@ describe('index', () => {
280
311
  const credential = await sdjwt.issue(
281
312
  {
282
313
  foo: 'bar',
314
+ iss: 'Issuer',
315
+ iat: new Date().getTime(),
316
+ vct: '',
283
317
  },
284
318
  {
285
319
  _sd: ['foo'],
@@ -289,7 +323,6 @@ describe('index', () => {
289
323
  const presentation = await sdjwt.present(credential, ['foo'], {
290
324
  kb: {
291
325
  payload: {
292
- sd_hash: 'sha-256',
293
326
  aud: '1',
294
327
  iat: 1,
295
328
  nonce: '342',
@@ -305,7 +338,7 @@ describe('index', () => {
305
338
 
306
339
  test('kbSigner not found', async () => {
307
340
  const { signer, verifier } = createSignerVerifier();
308
- const sdjwt = new SDJwtInstance({
341
+ const sdjwt = new TestInstance({
309
342
  signer,
310
343
  verifier,
311
344
  hasher: digest,
@@ -318,6 +351,9 @@ describe('index', () => {
318
351
  const credential = await sdjwt.issue(
319
352
  {
320
353
  foo: 'bar',
354
+ iss: 'Issuer',
355
+ iat: new Date().getTime(),
356
+ vct: '',
321
357
  },
322
358
  {
323
359
  _sd: ['foo'],
@@ -327,7 +363,6 @@ describe('index', () => {
327
363
  const presentation = await sdjwt.present(credential, ['foo'], {
328
364
  kb: {
329
365
  payload: {
330
- sd_hash: 'sha-256',
331
366
  aud: '1',
332
367
  iat: 1,
333
368
  nonce: '342',
@@ -341,7 +376,7 @@ describe('index', () => {
341
376
 
342
377
  test('kbVerifier not found', async () => {
343
378
  const { signer, verifier } = createSignerVerifier();
344
- const sdjwt = new SDJwtInstance({
379
+ const sdjwt = new TestInstance({
345
380
  signer,
346
381
  verifier,
347
382
  hasher: digest,
@@ -354,6 +389,9 @@ describe('index', () => {
354
389
  const credential = await sdjwt.issue(
355
390
  {
356
391
  foo: 'bar',
392
+ iss: 'Issuer',
393
+ iat: new Date().getTime(),
394
+ vct: '',
357
395
  },
358
396
  {
359
397
  _sd: ['foo'],
@@ -363,7 +401,6 @@ describe('index', () => {
363
401
  const presentation = await sdjwt.present(credential, ['foo'], {
364
402
  kb: {
365
403
  payload: {
366
- sd_hash: 'sha-256',
367
404
  aud: '1',
368
405
  iat: 1,
369
406
  nonce: '342',
@@ -376,4 +413,47 @@ describe('index', () => {
376
413
  expect(e).toBeDefined();
377
414
  }
378
415
  });
416
+
417
+ test('kbSignAlg not found', async () => {
418
+ const { signer, verifier } = createSignerVerifier();
419
+ const sdjwt = new TestInstance({
420
+ signer,
421
+ verifier,
422
+ hasher: digest,
423
+ saltGenerator: generateSalt,
424
+ kbSigner: signer,
425
+ signAlg: 'EdDSA',
426
+ });
427
+
428
+ const credential = await sdjwt.issue(
429
+ {
430
+ foo: 'bar',
431
+ iss: 'Issuer',
432
+ iat: new Date().getTime(),
433
+ vct: '',
434
+ },
435
+ {
436
+ _sd: ['foo'],
437
+ },
438
+ );
439
+
440
+ const presentation = sdjwt.present(credential, ['foo'], {
441
+ kb: {
442
+ payload: {
443
+ sd_hash: 'sha-256',
444
+ aud: '1',
445
+ iat: 1,
446
+ nonce: '342',
447
+ },
448
+ },
449
+ });
450
+ expect(presentation).rejects.toThrow(
451
+ 'Key Binding sign algorithm not specified',
452
+ );
453
+ });
454
+
455
+ test('hasher is not found', () => {
456
+ const sdjwt = new TestInstance({});
457
+ expect(sdjwt.keys('')).rejects.toThrow('Hasher not found');
458
+ });
379
459
  });
@@ -1,10 +1,11 @@
1
1
  import Crypto from 'node:crypto';
2
- import { SDJwtInstance } from '../src';
3
- import { DisclosureFrame, Signer, Verifier } from '@sd-jwt/types';
2
+ import { SdJwtPayload } from '../src';
3
+ import { DisclosureFrame, SD, Signer, Verifier } from '@sd-jwt/types';
4
4
  import fs from 'fs';
5
5
  import path from 'path';
6
6
  import { describe, expect, test } from 'vitest';
7
7
  import { digest, generateSalt } from '@sd-jwt/crypto-nodejs';
8
+ import { TestInstance } from '../src/test/index.spec';
8
9
 
9
10
  export const createSignerVerifier = () => {
10
11
  const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
@@ -26,7 +27,7 @@ export const createSignerVerifier = () => {
26
27
  describe('App', () => {
27
28
  test('Example', async () => {
28
29
  const { signer, verifier } = createSignerVerifier();
29
- const sdjwt = new SDJwtInstance({
30
+ const sdjwt = new TestInstance({
30
31
  signer,
31
32
  signAlg: 'EdDSA',
32
33
  verifier,
@@ -192,7 +193,7 @@ describe('App', () => {
192
193
  async function JSONtest(filename: string) {
193
194
  const test = loadTestJsonFile(filename);
194
195
  const { signer, verifier } = createSignerVerifier();
195
- const sdjwt = new SDJwtInstance({
196
+ const sdjwt = new TestInstance({
196
197
  signer,
197
198
  signAlg: 'EdDSA',
198
199
  verifier,
@@ -234,8 +235,8 @@ async function JSONtest(filename: string) {
234
235
  }
235
236
 
236
237
  type TestJson = {
237
- claims: object;
238
- disclosureFrame: DisclosureFrame<object>;
238
+ claims: SdJwtPayload;
239
+ disclosureFrame: DisclosureFrame<SdJwtPayload>;
239
240
  presentationKeys: string[];
240
241
  presenatedClaims: object;
241
242
  requiredClaimKeys: string[];