@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,63 @@
1
+ {
2
+ "claims": {
3
+ "array_with_recursive_sd": [
4
+ "boring",
5
+ {
6
+ "foo": "bar",
7
+ "baz": {
8
+ "qux": "quxx"
9
+ }
10
+ },
11
+ ["foo", "bar"]
12
+ ],
13
+ "test2": ["foo", "bar"]
14
+ },
15
+ "disclosureFrame": {
16
+ "array_with_recursive_sd": {
17
+ "1": {
18
+ "_sd": ["baz"]
19
+ },
20
+ "2": {
21
+ "_sd": [0, 1]
22
+ }
23
+ },
24
+ "test2": {
25
+ "_sd": [0, 1]
26
+ }
27
+ },
28
+ "presentationFrames": {
29
+ "array_with_recursive_sd": {
30
+ "1": {
31
+ "baz": true
32
+ },
33
+ "2": {
34
+ "1": true
35
+ }
36
+ },
37
+ "test2": {
38
+ "0": true,
39
+ "1": true
40
+ }
41
+ },
42
+ "presentedClaims": {
43
+ "array_with_recursive_sd": [
44
+ "boring",
45
+ {
46
+ "foo": "bar",
47
+ "baz": {
48
+ "qux": "quxx"
49
+ }
50
+ },
51
+ ["bar"]
52
+ ],
53
+ "test2": ["foo", "bar"]
54
+ },
55
+ "requiredClaimKeys": [
56
+ "array_with_recursive_sd.1",
57
+ "array_with_recursive_sd.2",
58
+ "array_with_recursive_sd.1.baz",
59
+ "array_with_recursive_sd.2.1",
60
+ "test2.0",
61
+ "test2.1"
62
+ ]
63
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "claims": {
3
+ "firstname": "John",
4
+ "lastname": "Doe",
5
+ "ssn": "123-45-6789",
6
+ "id": "1234",
7
+ "data": {
8
+ "firstname": "John",
9
+ "lastname": "Doe",
10
+ "ssn": "123-45-6789",
11
+ "list": [{ "r": "1" }, "b", "c"]
12
+ },
13
+ "data2": {
14
+ "hi": "bye"
15
+ }
16
+ },
17
+ "disclosureFrame": {
18
+ "_sd": ["firstname", "id", "data2"],
19
+ "data": {
20
+ "_sd": ["list"],
21
+ "_sd_decoy": 2,
22
+ "list": {
23
+ "_sd": [0, 2],
24
+ "_sd_decoy": 1,
25
+ "0": {
26
+ "_sd": ["r"]
27
+ }
28
+ }
29
+ },
30
+ "data2": {
31
+ "_sd": ["hi"]
32
+ }
33
+ },
34
+ "presentationFrames": { "firstname": true, "id": true },
35
+ "presentedClaims": {
36
+ "lastname": "Doe",
37
+ "ssn": "123-45-6789",
38
+ "data": { "firstname": "John", "lastname": "Doe", "ssn": "123-45-6789" },
39
+ "id": "1234",
40
+ "firstname": "John"
41
+ },
42
+ "requiredClaimKeys": ["firstname", "id", "data.ssn"]
43
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "claims": {
3
+ "sub": "john_deo_42",
4
+ "given_name": "John",
5
+ "family_name": "Deo",
6
+ "email": "johndeo@example.com",
7
+ "phone": "+1-202-555-0101",
8
+ "address": {
9
+ "street_address": "123 Main St",
10
+ "locality": "Anytown",
11
+ "region": "Anystate",
12
+ "country": "US"
13
+ },
14
+ "birthdate": "1940-01-01"
15
+ },
16
+ "disclosureFrame": {
17
+ "_sd": [
18
+ "sub",
19
+ "given_name",
20
+ "family_name",
21
+ "email",
22
+ "phone",
23
+ "address",
24
+ "birthdate"
25
+ ]
26
+ },
27
+ "presentationFrames": {
28
+ "given_name": true,
29
+ "family_name": true,
30
+ "address": true
31
+ },
32
+ "presentedClaims": {
33
+ "given_name": "John",
34
+ "family_name": "Deo",
35
+ "address": {
36
+ "street_address": "123 Main St",
37
+ "locality": "Anytown",
38
+ "region": "Anystate",
39
+ "country": "US"
40
+ }
41
+ },
42
+ "requiredClaimKeys": [
43
+ "given_name",
44
+ "family_name",
45
+ "address.street_address",
46
+ "address.country"
47
+ ]
48
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "claims": {
3
+ "sub": "john_deo_42",
4
+ "given_name": "John",
5
+ "family_name": "Deo",
6
+ "email": "johndeo@example.com",
7
+ "phone": "+1-202-555-0101",
8
+ "address": {
9
+ "street_address": "123 Main St",
10
+ "locality": "Anytown",
11
+ "region": "Anystate",
12
+ "country": "US"
13
+ },
14
+ "birthdate": "1940-01-01"
15
+ },
16
+ "disclosureFrame": {
17
+ "_sd": [
18
+ "sub",
19
+ "given_name",
20
+ "family_name",
21
+ "email",
22
+ "phone",
23
+ "address",
24
+ "birthdate"
25
+ ]
26
+ },
27
+ "presentationFrames": {
28
+ "given_name": true,
29
+ "family_name": true,
30
+ "address": true
31
+ },
32
+ "presentedClaims": {
33
+ "given_name": "John",
34
+ "family_name": "Deo",
35
+ "address": {
36
+ "street_address": "123 Main St",
37
+ "locality": "Anytown",
38
+ "region": "Anystate",
39
+ "country": "US"
40
+ }
41
+ },
42
+ "requiredClaimKeys": [
43
+ "given_name",
44
+ "family_name",
45
+ "address.street_address",
46
+ "address.country"
47
+ ]
48
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "claims": {
3
+ "sub": "john_deo_42",
4
+ "given_name": "John",
5
+ "family_name": "Deo",
6
+ "email": "johndeo@example.com",
7
+ "phone": "+1-202-555-0101",
8
+ "address": {
9
+ "street_address": "123 Main St",
10
+ "locality": "Anytown",
11
+ "region": "Anystate",
12
+ "country": "US"
13
+ },
14
+ "birthdate": "1940-01-01"
15
+ },
16
+ "disclosureFrame": {
17
+ "_sd": [
18
+ "sub",
19
+ "given_name",
20
+ "family_name",
21
+ "email",
22
+ "phone",
23
+ "address",
24
+ "birthdate"
25
+ ]
26
+ },
27
+ "presentationFrames": {
28
+ "given_name": true,
29
+ "family_name": true,
30
+ "address": true
31
+ },
32
+ "presentedClaims": {
33
+ "given_name": "John",
34
+ "family_name": "Deo",
35
+ "address": {
36
+ "street_address": "123 Main St",
37
+ "locality": "Anytown",
38
+ "region": "Anystate",
39
+ "country": "US"
40
+ }
41
+ },
42
+ "requiredClaimKeys": [
43
+ "given_name",
44
+ "family_name",
45
+ "address.street_address",
46
+ "address.country"
47
+ ]
48
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "claims": {
3
+ "recursive": [
4
+ "boring",
5
+ {
6
+ "foo": "bar",
7
+ "baz": {
8
+ "qux": "quxx"
9
+ }
10
+ },
11
+ ["foo", "bar"]
12
+ ],
13
+ "test2": ["foo", "bar"]
14
+ },
15
+ "disclosureFrame": {},
16
+ "presentationFrames": {},
17
+ "presentedClaims": {
18
+ "recursive": [
19
+ "boring",
20
+ {
21
+ "foo": "bar",
22
+ "baz": {
23
+ "qux": "quxx"
24
+ }
25
+ },
26
+ ["foo", "bar"]
27
+ ],
28
+ "test2": ["foo", "bar"]
29
+ },
30
+ "requiredClaimKeys": [
31
+ "recursive.0",
32
+ "recursive.1.baz.qux",
33
+ "recursive.2.1",
34
+ "test2.1"
35
+ ]
36
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "claims": {
3
+ "value_Data_types": {
4
+ "test_null": null,
5
+ "test_int": 42,
6
+ "test_float": 3.14,
7
+ "test_str": "foo",
8
+ "test_bool": true,
9
+ "test_arr": ["Test"],
10
+ "test_object": {
11
+ "foo": "bar"
12
+ }
13
+ }
14
+ },
15
+ "disclosureFrame": {
16
+ "value_Data_types": {
17
+ "_sd": [
18
+ "test_null",
19
+ "test_int",
20
+ "test_float",
21
+ "test_str",
22
+ "test_bool",
23
+ "test_arr",
24
+ "test_object"
25
+ ]
26
+ }
27
+ },
28
+ "presentationFrames": {
29
+ "value_Data_types": {
30
+ "test_null": true,
31
+ "test_int": true,
32
+ "test_float": true,
33
+ "test_str": true,
34
+ "test_bool": true,
35
+ "test_arr": true,
36
+ "test_object": true
37
+ }
38
+ },
39
+ "presentedClaims": {
40
+ "value_Data_types": {
41
+ "test_null": null,
42
+ "test_int": 42,
43
+ "test_float": 3.14,
44
+ "test_str": "foo",
45
+ "test_bool": true,
46
+ "test_arr": ["Test"],
47
+ "test_object": {
48
+ "foo": "bar"
49
+ }
50
+ }
51
+ },
52
+ "requiredClaimKeys": [
53
+ "value_Data_types.test_null",
54
+ "value_Data_types.test_int",
55
+ "value_Data_types.test_float",
56
+ "value_Data_types.test_str",
57
+ "value_Data_types.test_bool",
58
+ "value_Data_types.test_arr",
59
+ "value_Data_types.test_object",
60
+ "value_Data_types.test_object.foo"
61
+ ]
62
+ }
@@ -0,0 +1,117 @@
1
+ {
2
+ "claims": {
3
+ "foo": ["one", "two"],
4
+ "bar": {
5
+ "red": 1,
6
+ "green": 2
7
+ },
8
+ "qux": [["blue", "yellow"]],
9
+ "baz": [["orange", "purple"], ["black", "white"]],
10
+ "animals": {
11
+ "snake": {
12
+ "name": "python",
13
+ "age": 10
14
+ },
15
+ "bird": {
16
+ "name": "eagle",
17
+ "age": 20
18
+ }
19
+ }
20
+ },
21
+ "disclosureFrame": {
22
+ "foo": {
23
+ "_sd": [0, 1]
24
+ },
25
+ "bar": {
26
+ "_sd": ["red", "green"]
27
+ },
28
+ "qux": {
29
+ "_sd": [0],
30
+ "0": {
31
+ "_sd": [0, 1]
32
+ }
33
+ },
34
+ "baz": {
35
+ "_sd": [0, 1],
36
+ "0": {
37
+ "_sd": [0, 1]
38
+ },
39
+ "1": {
40
+ "_sd": [0, 1]
41
+ }
42
+ },
43
+ "animals": {
44
+ "_sd": ["snake", "bird"],
45
+ "snake": {
46
+ "_sd": ["name", "age"]
47
+ },
48
+ "bird": {
49
+ "_sd": ["name", "age"]
50
+ }
51
+ }
52
+ },
53
+ "presentationFrames": {
54
+ "foo": {
55
+ "1": true
56
+ },
57
+ "bar": {
58
+ "green": true
59
+ },
60
+ "qux": {
61
+ "0": {
62
+ "0": true,
63
+ "1": true
64
+ },
65
+ "1": {
66
+ "0": true,
67
+ "1": true
68
+ }
69
+ },
70
+ "baz": {
71
+ "0": {
72
+ "0": true,
73
+ "1": true
74
+ },
75
+ "1": {
76
+ "0": true,
77
+ "1": true
78
+ }
79
+ },
80
+ "animals": {
81
+ "snake": {
82
+ "age": true
83
+ },
84
+ "bird": {
85
+ "age": true
86
+ }
87
+ }
88
+ },
89
+ "presentedClaims": {
90
+ "foo": ["two"],
91
+ "bar": {
92
+ "green": 2
93
+ },
94
+ "qux": [["blue", "yellow"]],
95
+ "baz": [["orange", "purple"], ["black", "white"]],
96
+ "animals": {
97
+ "snake": {
98
+ "age": 10
99
+ },
100
+ "bird": {
101
+ "age": 20
102
+ }
103
+ }
104
+ },
105
+ "requiredClaimKeys": [
106
+ "foo.1",
107
+ "bar.green",
108
+ "qux.0.0",
109
+ "qux.0.1",
110
+ "baz.0.0",
111
+ "baz.0.1",
112
+ "baz.1.0",
113
+ "baz.1.1",
114
+ "animals.snake.age",
115
+ "animals.bird.age"
116
+ ]
117
+ }
@@ -0,0 +1,150 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { unpackObj } from '../src/decode';
3
+ import { Disclosure } from '../src/utils';
4
+
5
+ /**
6
+ * Tests for RFC 9901 validation checks added to unpackObj / unpackObjInternal.
7
+ *
8
+ * Section 7.1 step 4: Duplicate digest rejection
9
+ * Section 7.1 step 5: Unreferenced disclosure rejection
10
+ * Section 7.1 step 3c.ii.3: Claim name collision rejection
11
+ */
12
+
13
+ const makeDisclosure = (
14
+ digest: string,
15
+ key: string | undefined,
16
+ value: unknown,
17
+ ) =>
18
+ Disclosure.fromArray(key ? ['salt', key, value] : ['salt', value], {
19
+ digest,
20
+ encoded: `encoded-${digest}`,
21
+ });
22
+
23
+ describe('RFC 9901 validation', () => {
24
+ // ──────────────────────────────────────────
25
+ // 7.1 step 4 — Duplicate digest in _sd array
26
+ // ──────────────────────────────────────────
27
+ test('rejects duplicate digest in _sd array', () => {
28
+ const digest = 'abc123';
29
+ const payload = { _sd: [digest, digest] };
30
+ const map: Record<string, Disclosure> = {
31
+ [digest]: makeDisclosure(digest, 'foo', 'bar'),
32
+ };
33
+ expect(() => unpackObj(payload, map)).toThrow(
34
+ 'Duplicate digest found in SD-JWT payload',
35
+ );
36
+ });
37
+
38
+ // ──────────────────────────────────────────
39
+ // 7.1 step 4 — Duplicate digest across nested _sd
40
+ // ──────────────────────────────────────────
41
+ test('rejects duplicate digest across nested _sd arrays', () => {
42
+ const digest = 'dup1';
43
+ const payload = {
44
+ _sd: [digest],
45
+ nested: {
46
+ _sd: [digest],
47
+ },
48
+ };
49
+ const map: Record<string, Disclosure> = {
50
+ [digest]: makeDisclosure(digest, 'x', 'y'),
51
+ };
52
+ expect(() => unpackObj(payload, map)).toThrow(
53
+ 'Duplicate digest found in SD-JWT payload',
54
+ );
55
+ });
56
+
57
+ // ──────────────────────────────────────────
58
+ // 7.1 step 4 — Duplicate digest in array items
59
+ // ──────────────────────────────────────────
60
+ test('rejects duplicate digest in array element disclosures', () => {
61
+ const digest = 'arrdup';
62
+ const payload = {
63
+ arr: [{ '...': digest }, { '...': digest }],
64
+ };
65
+ const map: Record<string, Disclosure> = {
66
+ [digest]: makeDisclosure(digest, undefined, 'val'),
67
+ };
68
+ expect(() => unpackObj(payload, map)).toThrow(
69
+ 'Duplicate digest found in SD-JWT payload',
70
+ );
71
+ });
72
+
73
+ // ──────────────────────────────────────────
74
+ // 7.1 step 5 — Unreferenced disclosure
75
+ // ──────────────────────────────────────────
76
+ test('rejects unreferenced disclosure', () => {
77
+ const usedDigest = 'used1';
78
+ const unusedDigest = 'unused1';
79
+ const payload = { _sd: [usedDigest] };
80
+ const map: Record<string, Disclosure> = {
81
+ [usedDigest]: makeDisclosure(usedDigest, 'a', 1),
82
+ [unusedDigest]: makeDisclosure(unusedDigest, 'b', 2),
83
+ };
84
+ expect(() => unpackObj(payload, map)).toThrow(
85
+ 'Unreferenced disclosure(s) detected in SD-JWT',
86
+ );
87
+ });
88
+
89
+ test('rejects when no digests exist in payload but disclosures provided', () => {
90
+ const payload = { plain: 'value' };
91
+ const map: Record<string, Disclosure> = {
92
+ orphan: makeDisclosure('orphan', 'k', 'v'),
93
+ };
94
+ expect(() => unpackObj(payload, map)).toThrow(
95
+ 'Unreferenced disclosure(s) detected in SD-JWT',
96
+ );
97
+ });
98
+
99
+ // ──────────────────────────────────────────
100
+ // 7.1 step 3c.ii.3 — Claim name collision
101
+ // ──────────────────────────────────────────
102
+ test('rejects disclosed claim name that conflicts with plaintext key', () => {
103
+ const digest = 'col1';
104
+ // The payload has both a plaintext "name" and a disclosure that would add "name"
105
+ const payload = { name: 'Alice', _sd: [digest] };
106
+ const map: Record<string, Disclosure> = {
107
+ [digest]: makeDisclosure(digest, 'name', 'Mallory'),
108
+ };
109
+ expect(() => unpackObj(payload, map)).toThrow(
110
+ 'Disclosed claim name "name" conflicts with existing payload key',
111
+ );
112
+ });
113
+
114
+ // ──────────────────────────────────────────
115
+ // Positive: valid SD-JWT unpacks without error
116
+ // ──────────────────────────────────────────
117
+ test('unpacks valid SD-JWT without errors', () => {
118
+ const d1 = 'digest1';
119
+ const d2 = 'digest2';
120
+ const payload = { _sd: [d1, d2], plain: 'hello' };
121
+ const map: Record<string, Disclosure> = {
122
+ [d1]: makeDisclosure(d1, 'foo', 'bar'),
123
+ [d2]: makeDisclosure(d2, 'baz', 42),
124
+ };
125
+ const { unpackedObj, disclosureKeymap } = unpackObj(payload, map);
126
+ expect(unpackedObj).toEqual({ plain: 'hello', foo: 'bar', baz: 42 });
127
+ expect(disclosureKeymap).toEqual({ foo: d1, baz: d2 });
128
+ });
129
+
130
+ test('unpacks valid SD-JWT with array disclosures', () => {
131
+ const d1 = 'arrdig1';
132
+ const d2 = 'arrdig2';
133
+ const payload = {
134
+ arr: [{ '...': d1 }, 'plainItem', { '...': d2 }],
135
+ };
136
+ const map: Record<string, Disclosure> = {
137
+ [d1]: makeDisclosure(d1, undefined, 'secret1'),
138
+ [d2]: makeDisclosure(d2, undefined, 'secret2'),
139
+ };
140
+ const { unpackedObj } = unpackObj(payload, map);
141
+ expect(unpackedObj).toEqual({ arr: ['secret1', 'plainItem', 'secret2'] });
142
+ });
143
+
144
+ test('allows empty disclosure map with no _sd in payload', () => {
145
+ const payload = { plain: 'value' };
146
+ const map: Record<string, Disclosure> = {};
147
+ const { unpackedObj } = unpackObj(payload, map);
148
+ expect(unpackedObj).toEqual({ plain: 'value' });
149
+ });
150
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ }
7
+ }
@@ -0,0 +1,4 @@
1
+ // vite.config.ts
2
+ import { allEnvs } from '../../vitest.shared';
3
+
4
+ export default allEnvs;
@@ -1,28 +0,0 @@
1
- /// <reference types="node" />
2
- import { Buffer } from 'buffer';
3
- export declare class Base64url {
4
- /**
5
- *
6
- * Encode into base64url string
7
- *
8
- */
9
- static encode(input: string | Uint8Array | Buffer): string;
10
- /**
11
- *
12
- * Encode from JSON into a base64url string
13
- *
14
- */
15
- static encodeFromJson(input: Record<string, unknown> | Array<unknown>): string;
16
- /**
17
- *
18
- * Decode from base64url into JSON
19
- *
20
- */
21
- static decodeToJson<T extends Record<string, unknown> | Array<unknown> = Record<string, unknown>>(input: string): T;
22
- /**
23
- *
24
- * Decode from base64url into a byte array
25
- *
26
- */
27
- static decode(input: string): Uint8Array;
28
- }