@sd-jwt/core 0.1.0 → 0.1.1-next.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 (170) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +201 -0
  3. package/README.md +106 -0
  4. package/dist/index.d.mts +659 -0
  5. package/dist/index.d.ts +659 -0
  6. package/dist/index.js +1906 -0
  7. package/dist/index.mjs +1846 -0
  8. package/package.json +59 -47
  9. package/src/decode/decode.ts +376 -0
  10. package/src/decode/index.ts +1 -0
  11. package/src/decoy.ts +15 -0
  12. package/src/flattenJSON.ts +99 -0
  13. package/src/generalJSON.ts +148 -0
  14. package/src/index.ts +945 -0
  15. package/src/jwt.ts +190 -0
  16. package/src/kbjwt.ts +81 -0
  17. package/src/present/index.ts +1 -0
  18. package/src/present/present.ts +210 -0
  19. package/src/sdjwt.ts +354 -0
  20. package/src/test/decode/decode.spec.ts +202 -0
  21. package/src/test/decoy.spec.ts +30 -0
  22. package/src/test/flattenJSON.spec.ts +122 -0
  23. package/src/test/generalJSON.spec.ts +198 -0
  24. package/src/test/index.spec.ts +974 -0
  25. package/src/test/jwt.spec.ts +301 -0
  26. package/src/test/kbjwt.spec.ts +565 -0
  27. package/src/test/pass.spec.ts +7 -0
  28. package/src/test/present/present.spec.ts +305 -0
  29. package/src/test/sdjwt.spec.ts +384 -0
  30. package/src/test/types/type.spec.ts +88 -0
  31. package/src/test/utils/base64url.spec.ts +33 -0
  32. package/src/test/utils/disclosure.spec.ts +170 -0
  33. package/src/test/utils/error.spec.ts +15 -0
  34. package/src/types/index.ts +2 -0
  35. package/src/types/type.ts +249 -0
  36. package/src/types/verification-error.ts +55 -0
  37. package/src/utils/base64url.ts +6 -0
  38. package/src/utils/disclosure.ts +101 -0
  39. package/src/utils/error.ts +25 -0
  40. package/src/utils/index.ts +3 -0
  41. package/src/utils/strict-json.ts +17 -0
  42. package/test/app-e2e.spec.ts +292 -0
  43. package/test/array_data_types.json +31 -0
  44. package/test/array_full_sd.json +21 -0
  45. package/test/array_in_sd.json +13 -0
  46. package/test/array_nested_in_plain.json +29 -0
  47. package/test/array_none_disclosed.json +17 -0
  48. package/test/array_of_nulls.json +15 -0
  49. package/test/array_of_objects.json +65 -0
  50. package/test/array_of_scalars.json +19 -0
  51. package/test/array_recursive_sd.json +35 -0
  52. package/test/array_recursive_sd_some_disclosed.json +63 -0
  53. package/test/complex.json +43 -0
  54. package/test/header_mod.json +48 -0
  55. package/test/json_serialization.json +48 -0
  56. package/test/key_binding.json +48 -0
  57. package/test/no_sd.json +36 -0
  58. package/test/object_data_types.json +62 -0
  59. package/test/recursions.json +117 -0
  60. package/test/rfc9901-validation.spec.ts +150 -0
  61. package/tsconfig.json +7 -0
  62. package/vitest.config.mts +4 -0
  63. package/build/base64url.d.ts +0 -28
  64. package/build/base64url.js +0 -40
  65. package/build/base64url.js.map +0 -1
  66. package/build/hasherAlgorithm.d.ts +0 -70
  67. package/build/hasherAlgorithm.js +0 -75
  68. package/build/hasherAlgorithm.js.map +0 -1
  69. package/build/index.d.ts +0 -11
  70. package/build/index.js +0 -20
  71. package/build/index.js.map +0 -1
  72. package/build/jwt/compact.d.ts +0 -6
  73. package/build/jwt/compact.js +0 -27
  74. package/build/jwt/compact.js.map +0 -1
  75. package/build/jwt/error.d.ts +0 -2
  76. package/build/jwt/error.js +0 -7
  77. package/build/jwt/error.js.map +0 -1
  78. package/build/jwt/index.d.ts +0 -3
  79. package/build/jwt/index.js +0 -20
  80. package/build/jwt/index.js.map +0 -1
  81. package/build/jwt/jwt.d.ts +0 -204
  82. package/build/jwt/jwt.js +0 -324
  83. package/build/jwt/jwt.js.map +0 -1
  84. package/build/keyBinding/index.d.ts +0 -1
  85. package/build/keyBinding/index.js +0 -18
  86. package/build/keyBinding/index.js.map +0 -1
  87. package/build/keyBinding/keyBinding.d.ts +0 -56
  88. package/build/keyBinding/keyBinding.js +0 -99
  89. package/build/keyBinding/keyBinding.js.map +0 -1
  90. package/build/sdJwt/compact.d.ts +0 -8
  91. package/build/sdJwt/compact.js +0 -39
  92. package/build/sdJwt/compact.js.map +0 -1
  93. package/build/sdJwt/decoys.d.ts +0 -2
  94. package/build/sdJwt/decoys.js +0 -35
  95. package/build/sdJwt/decoys.js.map +0 -1
  96. package/build/sdJwt/disclosureFrame.d.ts +0 -7
  97. package/build/sdJwt/disclosureFrame.js +0 -89
  98. package/build/sdJwt/disclosureFrame.js.map +0 -1
  99. package/build/sdJwt/disclosureMapping.d.ts +0 -44
  100. package/build/sdJwt/disclosureMapping.js +0 -289
  101. package/build/sdJwt/disclosureMapping.js.map +0 -1
  102. package/build/sdJwt/disclosures.d.ts +0 -14
  103. package/build/sdJwt/disclosures.js +0 -59
  104. package/build/sdJwt/disclosures.js.map +0 -1
  105. package/build/sdJwt/error.d.ts +0 -2
  106. package/build/sdJwt/error.js +0 -7
  107. package/build/sdJwt/error.js.map +0 -1
  108. package/build/sdJwt/index.d.ts +0 -7
  109. package/build/sdJwt/index.js +0 -24
  110. package/build/sdJwt/index.js.map +0 -1
  111. package/build/sdJwt/presentationFrame.d.ts +0 -4
  112. package/build/sdJwt/presentationFrame.js +0 -73
  113. package/build/sdJwt/presentationFrame.js.map +0 -1
  114. package/build/sdJwt/sdJwt.d.ts +0 -204
  115. package/build/sdJwt/sdJwt.js +0 -383
  116. package/build/sdJwt/sdJwt.js.map +0 -1
  117. package/build/sdJwt/swapClaim.d.ts +0 -3
  118. package/build/sdJwt/swapClaim.js +0 -90
  119. package/build/sdJwt/swapClaim.js.map +0 -1
  120. package/build/sdJwt/types.d.ts +0 -5
  121. package/build/sdJwt/types.js +0 -3
  122. package/build/sdJwt/types.js.map +0 -1
  123. package/build/sdJwtVc/error.d.ts +0 -2
  124. package/build/sdJwtVc/error.js +0 -7
  125. package/build/sdJwtVc/error.js.map +0 -1
  126. package/build/sdJwtVc/index.d.ts +0 -2
  127. package/build/sdJwtVc/index.js +0 -19
  128. package/build/sdJwtVc/index.js.map +0 -1
  129. package/build/sdJwtVc/sdJwtVc.d.ts +0 -47
  130. package/build/sdJwtVc/sdJwtVc.js +0 -125
  131. package/build/sdJwtVc/sdJwtVc.js.map +0 -1
  132. package/build/signatureAndEncryptionAlgorithm.d.ts +0 -105
  133. package/build/signatureAndEncryptionAlgorithm.js +0 -110
  134. package/build/signatureAndEncryptionAlgorithm.js.map +0 -1
  135. package/build/types/disclosure.d.ts +0 -5
  136. package/build/types/disclosure.js +0 -3
  137. package/build/types/disclosure.js.map +0 -1
  138. package/build/types/frame.d.ts +0 -5
  139. package/build/types/frame.js +0 -3
  140. package/build/types/frame.js.map +0 -1
  141. package/build/types/hasher.d.ts +0 -14
  142. package/build/types/hasher.js +0 -3
  143. package/build/types/hasher.js.map +0 -1
  144. package/build/types/index.d.ts +0 -6
  145. package/build/types/index.js +0 -23
  146. package/build/types/index.js.map +0 -1
  147. package/build/types/present.d.ts +0 -2
  148. package/build/types/present.js +0 -3
  149. package/build/types/present.js.map +0 -1
  150. package/build/types/saltGenerator.d.ts +0 -17
  151. package/build/types/saltGenerator.js +0 -3
  152. package/build/types/saltGenerator.js.map +0 -1
  153. package/build/types/signer.d.ts +0 -2
  154. package/build/types/signer.js +0 -3
  155. package/build/types/signer.js.map +0 -1
  156. package/build/types/utils.d.ts +0 -5
  157. package/build/types/utils.js +0 -3
  158. package/build/types/utils.js.map +0 -1
  159. package/build/types/verifier.d.ts +0 -14
  160. package/build/types/verifier.js +0 -3
  161. package/build/types/verifier.js.map +0 -1
  162. package/build/utils/index.d.ts +0 -2
  163. package/build/utils/index.js +0 -19
  164. package/build/utils/index.js.map +0 -1
  165. package/build/utils/traverse.d.ts +0 -8
  166. package/build/utils/traverse.js +0 -29
  167. package/build/utils/traverse.js.map +0 -1
  168. package/build/utils/utils.d.ts +0 -7
  169. package/build/utils/utils.js +0 -109
  170. package/build/utils/utils.js.map +0 -1
@@ -0,0 +1,305 @@
1
+ import { hasher as digest } from '@owf/crypto';
2
+ import { describe, expect, test } from 'vitest';
3
+ import { decodeSdJwt, decodeSdJwtSync } from '../../decode';
4
+ import {
5
+ present,
6
+ presentableKeys,
7
+ presentableKeysSync,
8
+ presentSync,
9
+ type SerializedDisclosure,
10
+ selectDisclosures,
11
+ transformPresentationFrame,
12
+ } from '../../present';
13
+ import type { PresentationFrame } from '../../types';
14
+
15
+ describe('Present tests', () => {
16
+ test('presentableKeys', async () => {
17
+ const sdjwt =
18
+ 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~';
19
+ const decodedSdJwt = await decodeSdJwt(sdjwt, digest);
20
+ const keys = await presentableKeys(
21
+ decodedSdJwt.jwt.payload,
22
+ decodedSdJwt.disclosures,
23
+ digest,
24
+ );
25
+ expect(keys).toStrictEqual(['arr', 'arr.0', 'arr.2.a', 'foo', 'test.zzz']);
26
+ });
27
+
28
+ test('presentableKeys sync', () => {
29
+ const sdjwt =
30
+ 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~';
31
+ const decodedSdJwt = decodeSdJwtSync(sdjwt, digest);
32
+ const keys = presentableKeysSync(
33
+ decodedSdJwt.jwt.payload,
34
+ decodedSdJwt.disclosures,
35
+ digest,
36
+ );
37
+ expect(keys).toStrictEqual(['arr', 'arr.0', 'arr.2.a', 'foo', 'test.zzz']);
38
+ });
39
+
40
+ test('present', async () => {
41
+ const sdjwt =
42
+ 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~';
43
+ const presentedSdJwt = await present(
44
+ sdjwt,
45
+ {
46
+ foo: true,
47
+ arr: {
48
+ 0: true,
49
+ },
50
+ test: {
51
+ zzz: true,
52
+ },
53
+ },
54
+ digest,
55
+ );
56
+ expect(presentedSdJwt).toStrictEqual(
57
+ 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~',
58
+ );
59
+ });
60
+
61
+ test('present sync', () => {
62
+ const sdjwt =
63
+ 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~';
64
+ const presentedSdJwt = presentSync(
65
+ sdjwt,
66
+ {
67
+ foo: true,
68
+ arr: {
69
+ 0: true,
70
+ },
71
+ test: {
72
+ zzz: true,
73
+ },
74
+ },
75
+ digest,
76
+ );
77
+ expect(presentedSdJwt).toStrictEqual(
78
+ 'eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~',
79
+ );
80
+ });
81
+
82
+ test('transform an object for a presentation to a list', () => {
83
+ const claims = {
84
+ firstname: 'John',
85
+ lastname: 'Doe',
86
+ ssn: '123-45-6789',
87
+ id: '1234',
88
+ data: {
89
+ firstname: 'John',
90
+ lastname: 'Doe',
91
+ ssn: '123-45-6789',
92
+ list: [{ r: 'd' }, 'b', 'c'],
93
+ list2: ['1', '2', '3'],
94
+ list3: ['1', null, 2],
95
+ },
96
+ data2: {
97
+ hi: 'bye',
98
+ },
99
+ };
100
+
101
+ const presentFrame: PresentationFrame<typeof claims> = {
102
+ firstname: true,
103
+ data: {
104
+ firstname: true,
105
+ list: {
106
+ 1: true,
107
+ 0: {
108
+ r: true,
109
+ },
110
+ },
111
+ list2: {
112
+ 1: true,
113
+ },
114
+ list3: true,
115
+ },
116
+ data2: true,
117
+ };
118
+
119
+ const list = transformPresentationFrame(presentFrame);
120
+ expect(list).toStrictEqual([
121
+ 'firstname',
122
+ 'data',
123
+ 'data.firstname',
124
+ 'data.list',
125
+ 'data.list.0',
126
+ 'data.list.0.r',
127
+ 'data.list.1',
128
+ 'data.list2',
129
+ 'data.list2.1',
130
+ 'data.list3',
131
+ 'data2',
132
+ ]);
133
+ });
134
+
135
+ test('transform an object for a presentation to a list, but with faulty inputs', () => {
136
+ const claims = {
137
+ name: 'John',
138
+ address: {
139
+ city: 'New York',
140
+ street: '5th Avenue',
141
+ },
142
+ };
143
+
144
+ const obj: PresentationFrame<typeof claims> = {
145
+ name: false,
146
+ address: {
147
+ city: true,
148
+ street: true,
149
+ },
150
+ };
151
+ const list = transformPresentationFrame(obj);
152
+ expect(list).toStrictEqual(['address', 'address.city', 'address.street']);
153
+ });
154
+
155
+ test('select disclosures', () => {
156
+ const payload = {
157
+ lastname: 'Doe',
158
+ _sd: [
159
+ 'COnqXH7wGBFGR1ao12sDwTfu84Zs7cq92CZIg8ulIuU',
160
+ 'RrOc4ZfBVyD6iNlMbtmdokZOti322mOXfvIOBKvpuc4',
161
+ 'aXqInKwHoE1l8OM1VNUQDqTPeNUG1cMJVwVbxZJpP14',
162
+ ],
163
+ _sd_alg: 'sha-256',
164
+ };
165
+
166
+ const presentationFrame = {
167
+ firstname: true,
168
+ //ssn: true,
169
+ id: true,
170
+ };
171
+
172
+ const disclosures: SerializedDisclosure[] = [
173
+ {
174
+ digest: 'COnqXH7wGBFGR1ao12sDwTfu84Zs7cq92CZIg8ulIuU',
175
+ encoded: 'WyJiMDQ3NjBiOTgxMDgyM2ZhIiwiZmlyc3RuYW1lIiwiSm9obiJd',
176
+ salt: 'b04760b9810823fa',
177
+ key: 'firstname',
178
+ value: 'John',
179
+ },
180
+ {
181
+ digest: 'RrOc4ZfBVyD6iNlMbtmdokZOti322mOXfvIOBKvpuc4',
182
+ encoded: 'WyJjNTQwZWE4YjJhOTNmZDE1Iiwic3NuIiwiMTIzLTQ1LTY3ODkiXQ',
183
+ salt: 'c540ea8b2a93fd15',
184
+ key: 'ssn',
185
+ value: '123-45-6789',
186
+ },
187
+ {
188
+ digest: 'aXqInKwHoE1l8OM1VNUQDqTPeNUG1cMJVwVbxZJpP14',
189
+ encoded: 'WyI5N2YwNTVkZTk0NGFmNzI5IiwiaWQiLCIxMjM0Il0',
190
+ salt: '97f055de944af729',
191
+ key: 'id',
192
+ value: '1234',
193
+ },
194
+ ];
195
+
196
+ const selected = selectDisclosures(payload, disclosures, presentationFrame);
197
+ expect(selected).toStrictEqual([
198
+ {
199
+ digest: 'COnqXH7wGBFGR1ao12sDwTfu84Zs7cq92CZIg8ulIuU',
200
+ encoded: 'WyJiMDQ3NjBiOTgxMDgyM2ZhIiwiZmlyc3RuYW1lIiwiSm9obiJd',
201
+ salt: 'b04760b9810823fa',
202
+ key: 'firstname',
203
+ value: 'John',
204
+ },
205
+ {
206
+ digest: 'aXqInKwHoE1l8OM1VNUQDqTPeNUG1cMJVwVbxZJpP14',
207
+ encoded: 'WyI5N2YwNTVkZTk0NGFmNzI5IiwiaWQiLCIxMjM0Il0',
208
+ salt: '97f055de944af729',
209
+ key: 'id',
210
+ value: '1234',
211
+ },
212
+ ]);
213
+ });
214
+
215
+ test('select disclosures no input', () => {
216
+ const selected = selectDisclosures({}, [], {});
217
+ expect(selected).toStrictEqual([]);
218
+ });
219
+
220
+ test('select disclosures noting return', () => {
221
+ const payload = {
222
+ lastname: 'Doe',
223
+ _sd: [
224
+ 'COnqXH7wGBFGR1ao12sDwTfu84Zs7cq92CZIg8ulIuU',
225
+ 'RrOc4ZfBVyD6iNlMbtmdokZOti322mOXfvIOBKvpuc4',
226
+ 'aXqInKwHoE1l8OM1VNUQDqTPeNUG1cMJVwVbxZJpP14',
227
+ ],
228
+ _sd_alg: 'sha-256',
229
+ };
230
+
231
+ const presentationFrame = {};
232
+
233
+ const disclosures: SerializedDisclosure[] = [
234
+ {
235
+ digest: 'COnqXH7wGBFGR1ao12sDwTfu84Zs7cq92CZIg8ulIuU',
236
+ encoded: 'WyJiMDQ3NjBiOTgxMDgyM2ZhIiwiZmlyc3RuYW1lIiwiSm9obiJd',
237
+ salt: 'b04760b9810823fa',
238
+ key: 'firstname',
239
+ value: 'John',
240
+ },
241
+ {
242
+ digest: 'RrOc4ZfBVyD6iNlMbtmdokZOti322mOXfvIOBKvpuc4',
243
+ encoded: 'WyJjNTQwZWE4YjJhOTNmZDE1Iiwic3NuIiwiMTIzLTQ1LTY3ODkiXQ',
244
+ salt: 'c540ea8b2a93fd15',
245
+ key: 'ssn',
246
+ value: '123-45-6789',
247
+ },
248
+ {
249
+ digest: 'aXqInKwHoE1l8OM1VNUQDqTPeNUG1cMJVwVbxZJpP14',
250
+ encoded: 'WyI5N2YwNTVkZTk0NGFmNzI5IiwiaWQiLCIxMjM0Il0',
251
+ salt: '97f055de944af729',
252
+ key: 'id',
253
+ value: '1234',
254
+ },
255
+ ];
256
+ const selected = selectDisclosures(payload, disclosures, presentationFrame);
257
+ expect(selected).toStrictEqual([]);
258
+ });
259
+
260
+ test('expect missing digest', () => {
261
+ const payload = {
262
+ lastname: 'Doe',
263
+ _sd: [
264
+ 'COnqXH7wGBFGR1ao12sDwTfu84Zs7cq92CZIg8ulIuU',
265
+ 'RrOc4ZfBVyD6iNlMbtmdokZOti322mOXfvIOBKvpuc4',
266
+ 'aXqInKwHoE1l8OM1VNUQDqTPeNUG1cMJVwVbxZJpP14',
267
+ ],
268
+ _sd_alg: 'sha-256',
269
+ };
270
+
271
+ const presentationFrame = {
272
+ firstname: true,
273
+ //ssn: true,
274
+ id: true,
275
+ };
276
+
277
+ const disclosures: SerializedDisclosure[] = [
278
+ //@ts-expect-error
279
+ {
280
+ encoded: 'WyJiMDQ3NjBiOTgxMDgyM2ZhIiwiZmlyc3RuYW1lIiwiSm9obiJd',
281
+ salt: 'b04760b9810823fa',
282
+ key: 'firstname',
283
+ value: 'John',
284
+ },
285
+ {
286
+ digest: 'RrOc4ZfBVyD6iNlMbtmdokZOti322mOXfvIOBKvpuc4',
287
+ encoded: 'WyJjNTQwZWE4YjJhOTNmZDE1Iiwic3NuIiwiMTIzLTQ1LTY3ODkiXQ',
288
+ salt: 'c540ea8b2a93fd15',
289
+ key: 'ssn',
290
+ value: '123-45-6789',
291
+ },
292
+ {
293
+ digest: 'aXqInKwHoE1l8OM1VNUQDqTPeNUG1cMJVwVbxZJpP14',
294
+ encoded: 'WyI5N2YwNTVkZTk0NGFmNzI5IiwiaWQiLCIxMjM0Il0',
295
+ salt: '97f055de944af729',
296
+ key: 'id',
297
+ value: '1234',
298
+ },
299
+ ];
300
+
301
+ expect(() =>
302
+ selectDisclosures(payload, disclosures, presentationFrame),
303
+ ).toThrowError('Unreferenced disclosure(s) detected in SD-JWT');
304
+ });
305
+ });
@@ -0,0 +1,384 @@
1
+ import Crypto from 'node:crypto';
2
+ import { generateSalt, hasher } from '@owf/crypto';
3
+ import { describe, expect, test } from 'vitest';
4
+ import { createHashMapping, unpack } from '../decode';
5
+ import { Jwt } from '../jwt';
6
+ import { listKeys, pack, SDJwt } from '../sdjwt';
7
+ import type { DisclosureFrame, Signer } from '../types';
8
+ import { Disclosure } from '../utils';
9
+
10
+ const hash = { alg: 'SHA256', hasher };
11
+
12
+ describe('SD JWT', () => {
13
+ test('create and encode', async () => {
14
+ const { privateKey } = Crypto.generateKeyPairSync('ed25519');
15
+ const testSigner: Signer = async (data: string) => {
16
+ const sig = Crypto.sign(null, Buffer.from(data), privateKey);
17
+ return Buffer.from(sig).toString('base64url');
18
+ };
19
+
20
+ const jwt = new Jwt({
21
+ header: { alg: 'EdDSA' },
22
+ payload: { foo: 'bar' },
23
+ });
24
+
25
+ await jwt.sign(testSigner);
26
+ const sdJwt = new SDJwt({
27
+ jwt,
28
+ disclosures: [],
29
+ });
30
+ expect(sdJwt).toBeDefined();
31
+
32
+ const encoded = sdJwt.encodeSDJwt();
33
+ expect(encoded).toBeDefined();
34
+ });
35
+
36
+ test('decode', async () => {
37
+ const { privateKey } = Crypto.generateKeyPairSync('ed25519');
38
+ const testSigner: Signer = async (data: string) => {
39
+ const sig = Crypto.sign(null, Buffer.from(data), privateKey);
40
+ return Buffer.from(sig).toString('base64url');
41
+ };
42
+
43
+ const jwt = new Jwt({
44
+ header: { alg: 'EdDSA' },
45
+ payload: { foo: 'bar', _sd_alg: 'sha-256' },
46
+ });
47
+
48
+ await jwt.sign(testSigner);
49
+ const sdJwt = new SDJwt({
50
+ jwt,
51
+ disclosures: [],
52
+ });
53
+
54
+ const encoded = sdJwt.encodeSDJwt();
55
+
56
+ const newSdJwt = await SDJwt.fromEncode(encoded, hasher);
57
+ expect(newSdJwt).toBeDefined();
58
+ const newJwt = newSdJwt.jwt;
59
+ expect(newJwt?.header).toEqual(jwt.header);
60
+ expect(newJwt?.payload).toEqual(jwt.payload);
61
+ expect(newJwt?.signature).toEqual(jwt.signature);
62
+ });
63
+
64
+ test('decode compatibilty', async () => {
65
+ const { privateKey } = Crypto.generateKeyPairSync('ed25519');
66
+ const testSigner: Signer = async (data: string) => {
67
+ const sig = Crypto.sign(null, Buffer.from(data), privateKey);
68
+ return Buffer.from(sig).toString('base64url');
69
+ };
70
+
71
+ const jwt = new Jwt({
72
+ header: { alg: 'EdDSA' },
73
+ payload: { foo: 'bar' },
74
+ });
75
+
76
+ await jwt.sign(testSigner);
77
+ const sdJwt = new SDJwt({
78
+ jwt,
79
+ disclosures: [],
80
+ });
81
+
82
+ const encoded = sdJwt.encodeSDJwt();
83
+
84
+ const newSdJwt = await SDJwt.fromEncode(encoded, hasher);
85
+ expect(newSdJwt).toBeDefined();
86
+ const newJwt = newSdJwt.jwt;
87
+ expect(newJwt?.header).toEqual(jwt.header);
88
+ expect(newJwt?.payload).toEqual(jwt.payload);
89
+ expect(newJwt?.signature).toEqual(jwt.signature);
90
+ });
91
+
92
+ test('keys', async () => {
93
+ const { privateKey } = Crypto.generateKeyPairSync('ed25519');
94
+ const testSigner: Signer = async (data: string) => {
95
+ const sig = Crypto.sign(null, Buffer.from(data), privateKey);
96
+ return Buffer.from(sig).toString('base64url');
97
+ };
98
+ const jwt = new Jwt({
99
+ header: { alg: 'EdDSA' },
100
+ payload: { foo: 'bar' },
101
+ });
102
+
103
+ await jwt.sign(testSigner);
104
+ const sdJwt = new SDJwt({
105
+ jwt,
106
+ disclosures: [],
107
+ });
108
+
109
+ const keys = await sdJwt.keys(hasher);
110
+ expect(keys).toBeDefined();
111
+ expect(keys).toEqual(['foo']);
112
+ });
113
+
114
+ test('presentable keys', async () => {
115
+ const { privateKey } = Crypto.generateKeyPairSync('ed25519');
116
+ const testSigner: Signer = async (data: string) => {
117
+ const sig = Crypto.sign(null, Buffer.from(data), privateKey);
118
+ return Buffer.from(sig).toString('base64url');
119
+ };
120
+
121
+ const jwt = new Jwt({
122
+ header: { alg: 'EdDSA' },
123
+ payload: { foo: 'bar' },
124
+ });
125
+
126
+ await jwt.sign(testSigner);
127
+ const sdJwt = new SDJwt({
128
+ jwt,
129
+ disclosures: [],
130
+ });
131
+
132
+ const keys = await sdJwt.presentableKeys(hasher);
133
+ expect(keys).toBeDefined();
134
+ expect(keys).toEqual([]);
135
+ });
136
+
137
+ test('claims', async () => {
138
+ const { privateKey } = Crypto.generateKeyPairSync('ed25519');
139
+ const testSigner: Signer = async (data: string) => {
140
+ const sig = Crypto.sign(null, Buffer.from(data), privateKey);
141
+ return Buffer.from(sig).toString('base64url');
142
+ };
143
+
144
+ const jwt = new Jwt({
145
+ header: { alg: 'EdDSA' },
146
+ payload: { foo: 'bar' },
147
+ });
148
+
149
+ await jwt.sign(testSigner);
150
+ const sdJwt = new SDJwt({
151
+ jwt,
152
+ disclosures: [],
153
+ });
154
+
155
+ const claims = await sdJwt.getClaims(hasher);
156
+ expect(claims).toBeDefined();
157
+ expect(claims).toEqual({
158
+ foo: 'bar',
159
+ });
160
+ });
161
+
162
+ test('pack', async () => {
163
+ const claim = {
164
+ firstname: 'John',
165
+ lastname: 'Doe',
166
+ };
167
+
168
+ const { packedClaims, disclosures } = await pack(
169
+ claim,
170
+ {
171
+ _sd: ['firstname'],
172
+ },
173
+ hash,
174
+ generateSalt,
175
+ );
176
+
177
+ expect(disclosures).toBeDefined();
178
+ expect(packedClaims).toBeDefined();
179
+
180
+ expect(disclosures.length).toEqual(1);
181
+ expect(disclosures[0].key).toEqual('firstname');
182
+ expect(disclosures[0].value).toEqual('John');
183
+
184
+ expect(packedClaims._sd).toBeDefined();
185
+ expect(packedClaims._sd.length).toEqual(1);
186
+ expect(packedClaims.lastname).toEqual('Doe');
187
+ });
188
+
189
+ test('list keys', () => {
190
+ const data = {
191
+ a: {
192
+ b: {
193
+ c: 1,
194
+ },
195
+ d: [
196
+ {
197
+ e: 'fasfdsa',
198
+ f: 1234,
199
+ },
200
+ 3,
201
+ 4,
202
+ ],
203
+ },
204
+ g: [['h'], 'i'],
205
+ };
206
+
207
+ const keys = listKeys(data);
208
+ expect(keys).toEqual([
209
+ 'a',
210
+ 'a.b',
211
+ 'a.b.c',
212
+ 'a.d',
213
+ 'a.d.0',
214
+ 'a.d.0.e',
215
+ 'a.d.0.f',
216
+ 'a.d.1',
217
+ 'a.d.2',
218
+ 'g',
219
+ 'g.0',
220
+ 'g.0.0',
221
+ 'g.1',
222
+ ]);
223
+ });
224
+
225
+ test('presentable keys', async () => {
226
+ const claim = {
227
+ firstname: 'John',
228
+ lastname: 'Doe',
229
+ };
230
+
231
+ const { packedClaims, disclosures } = await pack(
232
+ claim,
233
+ {
234
+ _sd: ['firstname'],
235
+ },
236
+ hash,
237
+ generateSalt,
238
+ );
239
+
240
+ const { privateKey } = Crypto.generateKeyPairSync('ed25519');
241
+ const testSigner: Signer = async (data: string) => {
242
+ const sig = Crypto.sign(null, Buffer.from(data), privateKey);
243
+ return Buffer.from(sig).toString('base64url');
244
+ };
245
+ const jwt = new Jwt({
246
+ header: { alg: 'EdDSA' },
247
+ payload: packedClaims,
248
+ });
249
+
250
+ await jwt.sign(testSigner);
251
+ const sdJwt = new SDJwt({
252
+ jwt,
253
+ disclosures,
254
+ });
255
+
256
+ const keys = await sdJwt.presentableKeys(hasher);
257
+
258
+ expect(keys).toBeDefined();
259
+ expect(keys).toEqual(['firstname']);
260
+ });
261
+
262
+ test('hash map', async () => {
263
+ const claim = {
264
+ firstname: 'John',
265
+ lastname: 'Doe',
266
+ };
267
+
268
+ const { disclosures } = await pack(
269
+ claim,
270
+ {
271
+ _sd: ['firstname'],
272
+ },
273
+ hash,
274
+ generateSalt,
275
+ );
276
+
277
+ const mapping = await createHashMapping(disclosures, hash);
278
+ expect(mapping).toBeDefined();
279
+ expect(Object.keys(mapping).length).toEqual(1);
280
+ expect(mapping[Object.keys(mapping)[0]]).toBeInstanceOf(Disclosure);
281
+ });
282
+
283
+ test('unpack', async () => {
284
+ const claim = {
285
+ firstname: 'John',
286
+ lastname: 'Doe',
287
+ };
288
+
289
+ const { packedClaims, disclosures } = await pack(
290
+ claim,
291
+ {
292
+ _sd: ['firstname'],
293
+ },
294
+ hash,
295
+ generateSalt,
296
+ );
297
+
298
+ const { disclosureKeymap, unpackedObj } = await unpack(
299
+ packedClaims,
300
+ disclosures,
301
+ hasher,
302
+ );
303
+ expect(disclosureKeymap).toBeDefined();
304
+ expect(unpackedObj).toBeDefined();
305
+ expect(unpackedObj).toEqual({
306
+ firstname: 'John',
307
+ lastname: 'Doe',
308
+ });
309
+ expect(disclosureKeymap.firstname).toBeDefined();
310
+ expect(typeof disclosureKeymap.firstname).toEqual('string');
311
+ });
312
+
313
+ test('pack and unpack', async () => {
314
+ const claims = {
315
+ firstname: 'John',
316
+ lastname: 'Doe',
317
+ ssn: '123-45-6789',
318
+ id: '1234',
319
+ data: {
320
+ firstname: 'John',
321
+ lastname: 'Doe',
322
+ ssn: '123-45-6789',
323
+ list: [{ r: '1' }, 'b', 'c'],
324
+ },
325
+ data2: {
326
+ hi: 'bye',
327
+ },
328
+ };
329
+
330
+ const disclosureFrame: DisclosureFrame<typeof claims> = {
331
+ _sd: ['firstname', 'id', 'data2'],
332
+ data: {
333
+ _sd: ['list'],
334
+ _sd_decoy: 2,
335
+ list: {
336
+ _sd: [0, 2],
337
+ _sd_decoy: 1,
338
+ 0: {
339
+ _sd: ['r'],
340
+ },
341
+ },
342
+ },
343
+ data2: {
344
+ _sd: ['hi'],
345
+ },
346
+ };
347
+
348
+ const { packedClaims, disclosures } = await pack(
349
+ claims,
350
+ disclosureFrame,
351
+ hash,
352
+ generateSalt,
353
+ );
354
+ const { unpackedObj } = await unpack(packedClaims, disclosures, hasher);
355
+
356
+ expect(unpackedObj).toEqual(claims);
357
+ });
358
+
359
+ test('no disclosures', async () => {
360
+ const { privateKey } = Crypto.generateKeyPairSync('ed25519');
361
+ const testSigner: Signer = async (data: string) => {
362
+ const sig = Crypto.sign(null, Buffer.from(data), privateKey);
363
+ return Buffer.from(sig).toString('base64url');
364
+ };
365
+
366
+ const jwt = new Jwt({
367
+ header: { alg: 'EdDSA' },
368
+ payload: { foo: 'bar' },
369
+ });
370
+
371
+ await jwt.sign(testSigner);
372
+ const sdJwt = new SDJwt({
373
+ jwt,
374
+ disclosures: [],
375
+ });
376
+
377
+ const credential = sdJwt.encodeSDJwt();
378
+ const decoded = await SDJwt.decodeSDJwt(credential, hasher);
379
+ expect(jwt.header).toEqual(decoded.jwt.header);
380
+ expect(jwt.payload).toEqual(decoded.jwt.payload);
381
+ expect(jwt.signature).toEqual(decoded.jwt.signature);
382
+ expect(decoded.disclosures).toEqual([]);
383
+ });
384
+ });