@sd-jwt/decode 0.2.1 → 2.0.2-next.26

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 (66) hide show
  1. package/LICENSE +201 -0
  2. package/dist/index.d.mts +57 -0
  3. package/dist/index.d.ts +57 -0
  4. package/dist/index.js +282 -0
  5. package/dist/index.mjs +249 -0
  6. package/package.json +57 -46
  7. package/src/decode.ts +323 -0
  8. package/src/index.ts +1 -0
  9. package/src/test/decode.spec.ts +150 -0
  10. package/tsconfig.json +7 -0
  11. package/vitest.config.mts +4 -0
  12. package/README.md +0 -97
  13. package/build/disclosures/calculateDigest.d.ts +0 -4
  14. package/build/disclosures/calculateDigest.js +0 -14
  15. package/build/disclosures/calculateDigest.js.map +0 -1
  16. package/build/disclosures/decodePayload.d.ts +0 -2
  17. package/build/disclosures/decodePayload.js +0 -75
  18. package/build/disclosures/decodePayload.js.map +0 -1
  19. package/build/disclosures/fromArray.d.ts +0 -2
  20. package/build/disclosures/fromArray.js +0 -8
  21. package/build/disclosures/fromArray.js.map +0 -1
  22. package/build/disclosures/fromString.d.ts +0 -2
  23. package/build/disclosures/fromString.js +0 -11
  24. package/build/disclosures/fromString.js.map +0 -1
  25. package/build/disclosures/index.d.ts +0 -5
  26. package/build/disclosures/index.js +0 -22
  27. package/build/disclosures/index.js.map +0 -1
  28. package/build/disclosures/toArray.d.ts +0 -2
  29. package/build/disclosures/toArray.js +0 -6
  30. package/build/disclosures/toArray.js.map +0 -1
  31. package/build/index.d.ts +0 -5
  32. package/build/index.js +0 -20
  33. package/build/index.js.map +0 -1
  34. package/build/jwt/fromCompact.d.ts +0 -5
  35. package/build/jwt/fromCompact.js +0 -26
  36. package/build/jwt/fromCompact.js.map +0 -1
  37. package/build/jwt/index.d.ts +0 -1
  38. package/build/jwt/index.js +0 -18
  39. package/build/jwt/index.js.map +0 -1
  40. package/build/keyBinding/assertValid.d.ts +0 -0
  41. package/build/keyBinding/assertValid.js +0 -2
  42. package/build/keyBinding/assertValid.js.map +0 -1
  43. package/build/keyBinding/fromCompact.d.ts +0 -5
  44. package/build/keyBinding/fromCompact.js +0 -24
  45. package/build/keyBinding/fromCompact.js.map +0 -1
  46. package/build/keyBinding/index.d.ts +0 -2
  47. package/build/keyBinding/index.js +0 -19
  48. package/build/keyBinding/index.js.map +0 -1
  49. package/build/keyBinding/sdHash.d.ts +0 -2
  50. package/build/keyBinding/sdHash.js +0 -17
  51. package/build/keyBinding/sdHash.js.map +0 -1
  52. package/build/sdJwt/fromCompact.d.ts +0 -17
  53. package/build/sdJwt/fromCompact.js +0 -39
  54. package/build/sdJwt/fromCompact.js.map +0 -1
  55. package/build/sdJwt/index.d.ts +0 -1
  56. package/build/sdJwt/index.js +0 -18
  57. package/build/sdJwt/index.js.map +0 -1
  58. package/build/sdJwtVc/decode.d.ts +0 -12
  59. package/build/sdJwtVc/decode.js +0 -38
  60. package/build/sdJwtVc/decode.js.map +0 -1
  61. package/build/sdJwtVc/fromCompact.d.ts +0 -17
  62. package/build/sdJwtVc/fromCompact.js +0 -24
  63. package/build/sdJwtVc/fromCompact.js.map +0 -1
  64. package/build/sdJwtVc/index.d.ts +0 -2
  65. package/build/sdJwtVc/index.js +0 -19
  66. package/build/sdJwtVc/index.js.map +0 -1
package/dist/index.mjs ADDED
@@ -0,0 +1,249 @@
1
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
2
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
3
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
4
+ var __objRest = (source, exclude) => {
5
+ var target = {};
6
+ for (var prop in source)
7
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
8
+ target[prop] = source[prop];
9
+ if (source != null && __getOwnPropSymbols)
10
+ for (var prop of __getOwnPropSymbols(source)) {
11
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
12
+ target[prop] = source[prop];
13
+ }
14
+ return target;
15
+ };
16
+ var __async = (__this, __arguments, generator) => {
17
+ return new Promise((resolve, reject) => {
18
+ var fulfilled = (value) => {
19
+ try {
20
+ step(generator.next(value));
21
+ } catch (e) {
22
+ reject(e);
23
+ }
24
+ };
25
+ var rejected = (value) => {
26
+ try {
27
+ step(generator.throw(value));
28
+ } catch (e) {
29
+ reject(e);
30
+ }
31
+ };
32
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
33
+ step((generator = generator.apply(__this, __arguments)).next());
34
+ });
35
+ };
36
+
37
+ // src/decode.ts
38
+ import { Base64urlDecode, SDJWTException, Disclosure } from "@sd-jwt/utils";
39
+ import {
40
+ SD_DIGEST,
41
+ SD_LIST_KEY,
42
+ SD_SEPARATOR
43
+ } from "@sd-jwt/types";
44
+ var decodeJwt = (jwt) => {
45
+ const { 0: header, 1: payload, 2: signature, length } = jwt.split(".");
46
+ if (length !== 3) {
47
+ throw new SDJWTException("Invalid JWT as input");
48
+ }
49
+ return {
50
+ header: JSON.parse(Base64urlDecode(header)),
51
+ payload: JSON.parse(Base64urlDecode(payload)),
52
+ signature
53
+ };
54
+ };
55
+ var splitSdJwt = (sdjwt) => {
56
+ const [encodedJwt, ...encodedDisclosures] = sdjwt.split(SD_SEPARATOR);
57
+ if (encodedDisclosures.length === 0) {
58
+ return {
59
+ jwt: encodedJwt,
60
+ disclosures: []
61
+ };
62
+ }
63
+ const encodedKeyBindingJwt = encodedDisclosures.pop();
64
+ return {
65
+ jwt: encodedJwt,
66
+ disclosures: encodedDisclosures,
67
+ kbJwt: encodedKeyBindingJwt || void 0
68
+ };
69
+ };
70
+ var decodeSdJwt = (sdjwt, hasher) => __async(void 0, null, function* () {
71
+ const [encodedJwt, ...encodedDisclosures] = sdjwt.split(SD_SEPARATOR);
72
+ const jwt = decodeJwt(encodedJwt);
73
+ if (encodedDisclosures.length === 0) {
74
+ return {
75
+ jwt,
76
+ disclosures: []
77
+ };
78
+ }
79
+ const encodedKeyBindingJwt = encodedDisclosures.pop();
80
+ const kbJwt = encodedKeyBindingJwt ? decodeJwt(encodedKeyBindingJwt) : void 0;
81
+ const { _sd_alg } = getSDAlgAndPayload(jwt.payload);
82
+ const disclosures = yield Promise.all(
83
+ encodedDisclosures.map(
84
+ (ed) => Disclosure.fromEncode(ed, { alg: _sd_alg, hasher })
85
+ )
86
+ );
87
+ return {
88
+ jwt,
89
+ disclosures,
90
+ kbJwt
91
+ };
92
+ });
93
+ var decodeSdJwtSync = (sdjwt, hasher) => {
94
+ const [encodedJwt, ...encodedDisclosures] = sdjwt.split(SD_SEPARATOR);
95
+ const jwt = decodeJwt(encodedJwt);
96
+ if (encodedDisclosures.length === 0) {
97
+ return {
98
+ jwt,
99
+ disclosures: []
100
+ };
101
+ }
102
+ const encodedKeyBindingJwt = encodedDisclosures.pop();
103
+ const kbJwt = encodedKeyBindingJwt ? decodeJwt(encodedKeyBindingJwt) : void 0;
104
+ const { _sd_alg } = getSDAlgAndPayload(jwt.payload);
105
+ const disclosures = encodedDisclosures.map(
106
+ (ed) => Disclosure.fromEncodeSync(ed, { alg: _sd_alg, hasher })
107
+ );
108
+ return {
109
+ jwt,
110
+ disclosures,
111
+ kbJwt
112
+ };
113
+ };
114
+ var getClaims = (rawPayload, disclosures, hasher) => __async(void 0, null, function* () {
115
+ const { unpackedObj } = yield unpack(rawPayload, disclosures, hasher);
116
+ return unpackedObj;
117
+ });
118
+ var getClaimsSync = (rawPayload, disclosures, hasher) => {
119
+ const { unpackedObj } = unpackSync(rawPayload, disclosures, hasher);
120
+ return unpackedObj;
121
+ };
122
+ var unpackArray = (arr, map, prefix = "") => {
123
+ const keys = {};
124
+ const unpackedArray = [];
125
+ arr.forEach((item, idx) => {
126
+ if (typeof item === "object" && item !== null) {
127
+ const hash = item[SD_LIST_KEY];
128
+ if (hash) {
129
+ const disclosed = map[hash];
130
+ if (disclosed) {
131
+ const presentKey = prefix ? `${prefix}.${idx}` : `${idx}`;
132
+ keys[presentKey] = hash;
133
+ const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObj(
134
+ disclosed.value,
135
+ map,
136
+ presentKey
137
+ );
138
+ unpackedArray.push(unpackedObj);
139
+ Object.assign(keys, disclosureKeys);
140
+ }
141
+ } else {
142
+ const newKey = prefix ? `${prefix}.${idx}` : `${idx}`;
143
+ const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObj(
144
+ item,
145
+ map,
146
+ newKey
147
+ );
148
+ unpackedArray.push(unpackedObj);
149
+ Object.assign(keys, disclosureKeys);
150
+ }
151
+ } else {
152
+ unpackedArray.push(item);
153
+ }
154
+ });
155
+ return { unpackedObj: unpackedArray, disclosureKeymap: keys };
156
+ };
157
+ var unpackObj = (obj, map, prefix = "") => {
158
+ const keys = {};
159
+ if (typeof obj === "object" && obj !== null) {
160
+ if (Array.isArray(obj)) {
161
+ return unpackArray(obj, map, prefix);
162
+ }
163
+ for (const key in obj) {
164
+ if (key !== SD_DIGEST && key !== SD_LIST_KEY && typeof obj[key] === "object") {
165
+ const newKey = prefix ? `${prefix}.${key}` : key;
166
+ const { unpackedObj: unpackedObj2, disclosureKeymap: disclosureKeys } = unpackObj(
167
+ obj[key],
168
+ map,
169
+ newKey
170
+ );
171
+ obj[key] = unpackedObj2;
172
+ Object.assign(keys, disclosureKeys);
173
+ }
174
+ }
175
+ const _a = obj, { _sd } = _a, payload = __objRest(_a, ["_sd"]);
176
+ const claims = {};
177
+ if (_sd) {
178
+ for (const hash of _sd) {
179
+ const disclosed = map[hash];
180
+ if (disclosed == null ? void 0 : disclosed.key) {
181
+ const presentKey = prefix ? `${prefix}.${disclosed.key}` : disclosed.key;
182
+ keys[presentKey] = hash;
183
+ const { unpackedObj: unpackedObj2, disclosureKeymap: disclosureKeys } = unpackObj(
184
+ disclosed.value,
185
+ map,
186
+ presentKey
187
+ );
188
+ claims[disclosed.key] = unpackedObj2;
189
+ Object.assign(keys, disclosureKeys);
190
+ }
191
+ }
192
+ }
193
+ const unpackedObj = Object.assign(payload, claims);
194
+ return { unpackedObj, disclosureKeymap: keys };
195
+ }
196
+ return { unpackedObj: obj, disclosureKeymap: keys };
197
+ };
198
+ var createHashMapping = (disclosures, hash) => __async(void 0, null, function* () {
199
+ const map = {};
200
+ for (let i = 0; i < disclosures.length; i++) {
201
+ const disclosure = disclosures[i];
202
+ const digest = yield disclosure.digest(hash);
203
+ map[digest] = disclosure;
204
+ }
205
+ return map;
206
+ });
207
+ var createHashMappingSync = (disclosures, hash) => {
208
+ const map = {};
209
+ for (let i = 0; i < disclosures.length; i++) {
210
+ const disclosure = disclosures[i];
211
+ const digest = disclosure.digestSync(hash);
212
+ map[digest] = disclosure;
213
+ }
214
+ return map;
215
+ };
216
+ var getSDAlgAndPayload = (sdjwtPayload) => {
217
+ const _a = sdjwtPayload, { _sd_alg } = _a, payload = __objRest(_a, ["_sd_alg"]);
218
+ if (typeof _sd_alg !== "string") {
219
+ return { _sd_alg: "sha-256", payload };
220
+ }
221
+ return { _sd_alg, payload };
222
+ };
223
+ var unpack = (sdjwtPayload, disclosures, hasher) => __async(void 0, null, function* () {
224
+ const { _sd_alg, payload } = getSDAlgAndPayload(sdjwtPayload);
225
+ const hash = { hasher, alg: _sd_alg };
226
+ const map = yield createHashMapping(disclosures, hash);
227
+ return unpackObj(payload, map);
228
+ });
229
+ var unpackSync = (sdjwtPayload, disclosures, hasher) => {
230
+ const { _sd_alg, payload } = getSDAlgAndPayload(sdjwtPayload);
231
+ const hash = { hasher, alg: _sd_alg };
232
+ const map = createHashMappingSync(disclosures, hash);
233
+ return unpackObj(payload, map);
234
+ };
235
+ export {
236
+ createHashMapping,
237
+ createHashMappingSync,
238
+ decodeJwt,
239
+ decodeSdJwt,
240
+ decodeSdJwtSync,
241
+ getClaims,
242
+ getClaimsSync,
243
+ getSDAlgAndPayload,
244
+ splitSdJwt,
245
+ unpack,
246
+ unpackArray,
247
+ unpackObj,
248
+ unpackSync
249
+ };
package/package.json CHANGED
@@ -1,49 +1,60 @@
1
1
  {
2
- "name": "@sd-jwt/decode",
3
- "version": "0.2.1",
4
- "description": "Decode implementation of sd-jwt Draft 06 and sd-jwt-vc Draft 01",
5
- "license": "(MIT OR Apache-2.0)",
6
- "author": "Berend Sliedrecht <sliedrecht@berend.io>",
7
- "readme": "../../README.md",
8
- "keywords": [
9
- "jwt",
10
- "jwt-sd",
11
- "sd-jwt",
12
- "sd-jwt-vc",
13
- "decentralized-identity",
14
- "ssi",
15
- "oauth",
16
- "oauth2",
17
- "openid-connect"
2
+ "name": "@sd-jwt/decode",
3
+ "version": "2.0.2-next.26+278b4fa",
4
+ "description": "sd-jwt draft 7 implementation in typescript",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "rm -rf **/dist && tsup",
16
+ "lint": "biome lint ./src",
17
+ "test": "pnpm run test:node && pnpm run test:browser && pnpm run test:cov",
18
+ "test:node": "vitest run ./src/test/*.spec.ts --coverage",
19
+ "test:browser": "vitest run ./src/test/*.spec.ts --environment jsdom --coverage"
20
+ },
21
+ "keywords": [
22
+ "sd-jwt",
23
+ "sdjwt",
24
+ "sd-jwt-vc"
25
+ ],
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js"
29
+ },
30
+ "author": "Lukas.J.Han <lukas.j.han@gmail.com>",
31
+ "homepage": "https://github.com/openwallet-foundation-labs/sd-jwt-js/wiki",
32
+ "bugs": {
33
+ "url": "https://github.com/openwallet-foundation-labs/sd-jwt-js/issues"
34
+ },
35
+ "license": "Apache-2.0",
36
+ "devDependencies": {
37
+ "@sd-jwt/crypto-nodejs": "2.0.2-next.26+278b4fa"
38
+ },
39
+ "dependencies": {
40
+ "@sd-jwt/types": "2.0.2-next.26+278b4fa",
41
+ "@sd-jwt/utils": "2.0.2-next.26+278b4fa"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "tsup": {
47
+ "entry": [
48
+ "./src/index.ts"
18
49
  ],
19
- "repository": {
20
- "url": "https://github.com/berendsliedrecht/sd-jwt-ts",
21
- "type": "git"
22
- },
23
- "homepage": "https://github.com/berendsliedrecht/sd-jwt-ts",
24
- "bugs": {
25
- "url": "https://github.com/berendsliedrecht/sd-jwt-ts/issues",
26
- "email": "sliedrecht@berend.io"
27
- },
28
- "publishConfig": {
29
- "access": "public"
30
- },
31
- "main": "build/index.js",
32
- "files": [
33
- "build"
34
- ],
35
- "scripts": {
36
- "build": "tsc",
37
- "test": "node --require ts-node/register --test ./tests/*.test.ts"
38
- },
39
- "dependencies": {
40
- "@sd-jwt/types": "0.2.1",
41
- "@sd-jwt/utils": "0.2.1"
42
- },
43
- "devDependencies": {
44
- "@types/node": "*",
45
- "ts-node": "*",
46
- "typescript": "*"
47
- },
48
- "gitHead": "a1fb5fad0b938081e033233bae5569ae3b9fe8cb"
50
+ "sourceMap": true,
51
+ "splitting": false,
52
+ "clean": true,
53
+ "dts": true,
54
+ "format": [
55
+ "cjs",
56
+ "esm"
57
+ ]
58
+ },
59
+ "gitHead": "278b4faf53b544d274ecb4e14a9f90c2312af846"
49
60
  }
package/src/decode.ts ADDED
@@ -0,0 +1,323 @@
1
+ import { Base64urlDecode, SDJWTException, Disclosure } from '@sd-jwt/utils';
2
+ import {
3
+ Hasher,
4
+ HasherAndAlg,
5
+ SD_DIGEST,
6
+ SD_LIST_KEY,
7
+ SD_SEPARATOR,
8
+ } from '@sd-jwt/types';
9
+ import { HasherAndAlgSync, HasherSync } from '@sd-jwt/types/src/type';
10
+
11
+ export const decodeJwt = <
12
+ H extends Record<string, unknown>,
13
+ T extends Record<string, unknown>,
14
+ >(
15
+ jwt: string,
16
+ ): { header: H; payload: T; signature: string } => {
17
+ const { 0: header, 1: payload, 2: signature, length } = jwt.split('.');
18
+ if (length !== 3) {
19
+ throw new SDJWTException('Invalid JWT as input');
20
+ }
21
+
22
+ return {
23
+ header: JSON.parse(Base64urlDecode(header)),
24
+ payload: JSON.parse(Base64urlDecode(payload)),
25
+ signature: signature,
26
+ };
27
+ };
28
+
29
+ // Split the sdjwt into 3 parts: jwt, disclosures and keybinding jwt. each part is base64url encoded
30
+ // It's separated by the ~ character
31
+ //
32
+ // If there is no keybinding jwt, the third part will be undefined
33
+ // If there are no disclosures, the second part will be an empty array
34
+ export const splitSdJwt = (
35
+ sdjwt: string,
36
+ ): { jwt: string; disclosures: string[]; kbJwt?: string } => {
37
+ const [encodedJwt, ...encodedDisclosures] = sdjwt.split(SD_SEPARATOR);
38
+ if (encodedDisclosures.length === 0) {
39
+ // if input is just jwt, then return here.
40
+ // This is for compatibility with jwt
41
+ return {
42
+ jwt: encodedJwt,
43
+ disclosures: [],
44
+ };
45
+ }
46
+
47
+ const encodedKeyBindingJwt = encodedDisclosures.pop();
48
+ return {
49
+ jwt: encodedJwt,
50
+ disclosures: encodedDisclosures,
51
+ kbJwt: encodedKeyBindingJwt || undefined,
52
+ };
53
+ };
54
+
55
+ // Decode the sdjwt into the jwt, disclosures and keybinding jwt
56
+ // jwt, disclosures and keybinding jwt are also decoded
57
+ export const decodeSdJwt = async (
58
+ sdjwt: string,
59
+ hasher: Hasher,
60
+ ): Promise<DecodedSDJwt> => {
61
+ const [encodedJwt, ...encodedDisclosures] = sdjwt.split(SD_SEPARATOR);
62
+ const jwt = decodeJwt(encodedJwt);
63
+
64
+ if (encodedDisclosures.length === 0) {
65
+ // if input is just jwt, then return here.
66
+ // This is for compatibility with jwt
67
+ return {
68
+ jwt,
69
+ disclosures: [],
70
+ };
71
+ }
72
+
73
+ const encodedKeyBindingJwt = encodedDisclosures.pop();
74
+ const kbJwt = encodedKeyBindingJwt
75
+ ? decodeJwt(encodedKeyBindingJwt)
76
+ : undefined;
77
+
78
+ const { _sd_alg } = getSDAlgAndPayload(jwt.payload);
79
+
80
+ const disclosures = await Promise.all(
81
+ encodedDisclosures.map((ed) =>
82
+ Disclosure.fromEncode(ed, { alg: _sd_alg, hasher }),
83
+ ),
84
+ );
85
+
86
+ return {
87
+ jwt,
88
+ disclosures,
89
+ kbJwt,
90
+ };
91
+ };
92
+
93
+ export const decodeSdJwtSync = (
94
+ sdjwt: string,
95
+ hasher: HasherSync,
96
+ ): DecodedSDJwt => {
97
+ const [encodedJwt, ...encodedDisclosures] = sdjwt.split(SD_SEPARATOR);
98
+ const jwt = decodeJwt(encodedJwt);
99
+
100
+ if (encodedDisclosures.length === 0) {
101
+ // if input is just jwt, then return here.
102
+ // This is for compatibility with jwt
103
+ return {
104
+ jwt,
105
+ disclosures: [],
106
+ };
107
+ }
108
+
109
+ const encodedKeyBindingJwt = encodedDisclosures.pop();
110
+ const kbJwt = encodedKeyBindingJwt
111
+ ? decodeJwt(encodedKeyBindingJwt)
112
+ : undefined;
113
+
114
+ const { _sd_alg } = getSDAlgAndPayload(jwt.payload);
115
+
116
+ const disclosures = encodedDisclosures.map((ed) =>
117
+ Disclosure.fromEncodeSync(ed, { alg: _sd_alg, hasher }),
118
+ );
119
+
120
+ return {
121
+ jwt,
122
+ disclosures,
123
+ kbJwt,
124
+ };
125
+ };
126
+
127
+ // Get the claims from jwt and disclosures
128
+ // The digested values are matched with the disclosures and the claims are extracted
129
+ export const getClaims = async <T>(
130
+ rawPayload: Record<string, unknown>,
131
+ disclosures: Array<Disclosure>,
132
+ hasher: Hasher,
133
+ ): Promise<T> => {
134
+ const { unpackedObj } = await unpack(rawPayload, disclosures, hasher);
135
+ return unpackedObj as T;
136
+ };
137
+
138
+ export const getClaimsSync = <T>(
139
+ rawPayload: Record<string, unknown>,
140
+ disclosures: Array<Disclosure>,
141
+ hasher: HasherSync,
142
+ ): T => {
143
+ const { unpackedObj } = unpackSync(rawPayload, disclosures, hasher);
144
+ return unpackedObj as T;
145
+ };
146
+
147
+ export const unpackArray = (
148
+ arr: Array<unknown>,
149
+ map: Record<string, Disclosure>,
150
+ prefix = '',
151
+ ): { unpackedObj: unknown; disclosureKeymap: Record<string, string> } => {
152
+ const keys: Record<string, string> = {};
153
+ const unpackedArray: unknown[] = [];
154
+ arr.forEach((item, idx) => {
155
+ if (typeof item === 'object' && item !== null) {
156
+ const hash = (item as Record<string, string>)[SD_LIST_KEY];
157
+ if (hash) {
158
+ const disclosed = map[hash];
159
+ if (disclosed) {
160
+ const presentKey = prefix ? `${prefix}.${idx}` : `${idx}`;
161
+ keys[presentKey] = hash;
162
+
163
+ const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObj(
164
+ disclosed.value,
165
+ map,
166
+ presentKey,
167
+ );
168
+ unpackedArray.push(unpackedObj);
169
+ Object.assign(keys, disclosureKeys);
170
+ }
171
+ } else {
172
+ const newKey = prefix ? `${prefix}.${idx}` : `${idx}`;
173
+ const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObj(
174
+ item,
175
+ map,
176
+ newKey,
177
+ );
178
+ unpackedArray.push(unpackedObj);
179
+ Object.assign(keys, disclosureKeys);
180
+ }
181
+ } else {
182
+ unpackedArray.push(item);
183
+ }
184
+ });
185
+ return { unpackedObj: unpackedArray, disclosureKeymap: keys };
186
+ };
187
+
188
+ export const unpackObj = (
189
+ obj: unknown,
190
+ map: Record<string, Disclosure>,
191
+ prefix = '',
192
+ ): { unpackedObj: unknown; disclosureKeymap: Record<string, string> } => {
193
+ const keys: Record<string, string> = {};
194
+ if (typeof obj === 'object' && obj !== null) {
195
+ if (Array.isArray(obj)) {
196
+ return unpackArray(obj, map, prefix);
197
+ }
198
+
199
+ for (const key in obj) {
200
+ if (
201
+ key !== SD_DIGEST &&
202
+ key !== SD_LIST_KEY &&
203
+ typeof (obj as Record<string, unknown>)[key] === 'object'
204
+ ) {
205
+ const newKey = prefix ? `${prefix}.${key}` : key;
206
+ const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObj(
207
+ (obj as Record<string, unknown>)[key],
208
+ map,
209
+ newKey,
210
+ );
211
+ (obj as Record<string, unknown>)[key] = unpackedObj;
212
+ Object.assign(keys, disclosureKeys);
213
+ }
214
+ }
215
+
216
+ const { _sd, ...payload } = obj as Record<string, unknown> & {
217
+ _sd?: Array<string>;
218
+ };
219
+ const claims: Record<string, unknown> = {};
220
+ if (_sd) {
221
+ for (const hash of _sd) {
222
+ const disclosed = map[hash];
223
+ if (disclosed?.key) {
224
+ const presentKey = prefix
225
+ ? `${prefix}.${disclosed.key}`
226
+ : disclosed.key;
227
+ keys[presentKey] = hash;
228
+
229
+ const { unpackedObj, disclosureKeymap: disclosureKeys } = unpackObj(
230
+ disclosed.value,
231
+ map,
232
+ presentKey,
233
+ );
234
+ claims[disclosed.key] = unpackedObj;
235
+ Object.assign(keys, disclosureKeys);
236
+ }
237
+ }
238
+ }
239
+
240
+ const unpackedObj = Object.assign(payload, claims);
241
+ return { unpackedObj, disclosureKeymap: keys };
242
+ }
243
+ return { unpackedObj: obj, disclosureKeymap: keys };
244
+ };
245
+
246
+ // Creates a mapping of the digests of the disclosures to the actual disclosures
247
+ export const createHashMapping = async (
248
+ disclosures: Array<Disclosure>,
249
+ hash: HasherAndAlg,
250
+ ) => {
251
+ const map: Record<string, Disclosure> = {};
252
+ for (let i = 0; i < disclosures.length; i++) {
253
+ const disclosure = disclosures[i];
254
+ const digest = await disclosure.digest(hash);
255
+ map[digest] = disclosure;
256
+ }
257
+ return map;
258
+ };
259
+
260
+ export const createHashMappingSync = (
261
+ disclosures: Array<Disclosure>,
262
+ hash: HasherAndAlgSync,
263
+ ) => {
264
+ const map: Record<string, Disclosure> = {};
265
+ for (let i = 0; i < disclosures.length; i++) {
266
+ const disclosure = disclosures[i];
267
+ const digest = disclosure.digestSync(hash);
268
+ map[digest] = disclosure;
269
+ }
270
+ return map;
271
+ };
272
+
273
+ // Extract _sd_alg. If it is not present, it is assumed to be sha-256
274
+ export const getSDAlgAndPayload = (sdjwtPayload: Record<string, unknown>) => {
275
+ const { _sd_alg, ...payload } = sdjwtPayload;
276
+ if (typeof _sd_alg !== 'string') {
277
+ // This is for compatibility
278
+ return { _sd_alg: 'sha-256', payload };
279
+ }
280
+ return { _sd_alg, payload };
281
+ };
282
+
283
+ // Match the digests of the disclosures with the claims and extract the claims
284
+ // unpack function use unpackObj and unpackArray to recursively unpack the claims
285
+ export const unpack = async (
286
+ sdjwtPayload: Record<string, unknown>,
287
+ disclosures: Array<Disclosure>,
288
+ hasher: Hasher,
289
+ ) => {
290
+ const { _sd_alg, payload } = getSDAlgAndPayload(sdjwtPayload);
291
+ const hash = { hasher, alg: _sd_alg };
292
+ const map = await createHashMapping(disclosures, hash);
293
+
294
+ return unpackObj(payload, map);
295
+ };
296
+
297
+ export const unpackSync = (
298
+ sdjwtPayload: Record<string, unknown>,
299
+ disclosures: Array<Disclosure>,
300
+ hasher: HasherSync,
301
+ ) => {
302
+ const { _sd_alg, payload } = getSDAlgAndPayload(sdjwtPayload);
303
+ const hash = { hasher, alg: _sd_alg };
304
+ const map = createHashMappingSync(disclosures, hash);
305
+
306
+ return unpackObj(payload, map);
307
+ };
308
+
309
+ // This is the type of the object that is returned by the decodeSdJwt function
310
+ // It is a combination of the decoded jwt, the disclosures and the keybinding jwt
311
+ export type DecodedSDJwt = {
312
+ jwt: {
313
+ header: Record<string, unknown>;
314
+ payload: Record<string, unknown>; // raw payload of sd-jwt
315
+ signature: string;
316
+ };
317
+ disclosures: Array<Disclosure>;
318
+ kbJwt?: {
319
+ header: Record<string, unknown>;
320
+ payload: Record<string, unknown>;
321
+ signature: string;
322
+ };
323
+ };
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './decode';