@mcp-i/core 0.1.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 (226) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +390 -0
  3. package/dist/auth/handshake.d.ts +104 -0
  4. package/dist/auth/handshake.d.ts.map +1 -0
  5. package/dist/auth/handshake.js +230 -0
  6. package/dist/auth/handshake.js.map +1 -0
  7. package/dist/auth/index.d.ts +3 -0
  8. package/dist/auth/index.d.ts.map +1 -0
  9. package/dist/auth/index.js +2 -0
  10. package/dist/auth/index.js.map +1 -0
  11. package/dist/auth/types.d.ts +31 -0
  12. package/dist/auth/types.d.ts.map +1 -0
  13. package/dist/auth/types.js +7 -0
  14. package/dist/auth/types.js.map +1 -0
  15. package/dist/delegation/audience-validator.d.ts +9 -0
  16. package/dist/delegation/audience-validator.d.ts.map +1 -0
  17. package/dist/delegation/audience-validator.js +17 -0
  18. package/dist/delegation/audience-validator.js.map +1 -0
  19. package/dist/delegation/bitstring.d.ts +37 -0
  20. package/dist/delegation/bitstring.d.ts.map +1 -0
  21. package/dist/delegation/bitstring.js +117 -0
  22. package/dist/delegation/bitstring.js.map +1 -0
  23. package/dist/delegation/cascading-revocation.d.ts +45 -0
  24. package/dist/delegation/cascading-revocation.d.ts.map +1 -0
  25. package/dist/delegation/cascading-revocation.js +148 -0
  26. package/dist/delegation/cascading-revocation.js.map +1 -0
  27. package/dist/delegation/delegation-graph.d.ts +49 -0
  28. package/dist/delegation/delegation-graph.d.ts.map +1 -0
  29. package/dist/delegation/delegation-graph.js +99 -0
  30. package/dist/delegation/delegation-graph.js.map +1 -0
  31. package/dist/delegation/did-key-resolver.d.ts +64 -0
  32. package/dist/delegation/did-key-resolver.d.ts.map +1 -0
  33. package/dist/delegation/did-key-resolver.js +154 -0
  34. package/dist/delegation/did-key-resolver.js.map +1 -0
  35. package/dist/delegation/did-web-resolver.d.ts +83 -0
  36. package/dist/delegation/did-web-resolver.d.ts.map +1 -0
  37. package/dist/delegation/did-web-resolver.js +218 -0
  38. package/dist/delegation/did-web-resolver.js.map +1 -0
  39. package/dist/delegation/index.d.ts +21 -0
  40. package/dist/delegation/index.d.ts.map +1 -0
  41. package/dist/delegation/index.js +21 -0
  42. package/dist/delegation/index.js.map +1 -0
  43. package/dist/delegation/outbound-headers.d.ts +81 -0
  44. package/dist/delegation/outbound-headers.d.ts.map +1 -0
  45. package/dist/delegation/outbound-headers.js +139 -0
  46. package/dist/delegation/outbound-headers.js.map +1 -0
  47. package/dist/delegation/outbound-proof.d.ts +43 -0
  48. package/dist/delegation/outbound-proof.d.ts.map +1 -0
  49. package/dist/delegation/outbound-proof.js +52 -0
  50. package/dist/delegation/outbound-proof.js.map +1 -0
  51. package/dist/delegation/statuslist-manager.d.ts +44 -0
  52. package/dist/delegation/statuslist-manager.d.ts.map +1 -0
  53. package/dist/delegation/statuslist-manager.js +126 -0
  54. package/dist/delegation/statuslist-manager.js.map +1 -0
  55. package/dist/delegation/storage/memory-graph-storage.d.ts +70 -0
  56. package/dist/delegation/storage/memory-graph-storage.d.ts.map +1 -0
  57. package/dist/delegation/storage/memory-graph-storage.js +145 -0
  58. package/dist/delegation/storage/memory-graph-storage.js.map +1 -0
  59. package/dist/delegation/storage/memory-statuslist-storage.d.ts +19 -0
  60. package/dist/delegation/storage/memory-statuslist-storage.d.ts.map +1 -0
  61. package/dist/delegation/storage/memory-statuslist-storage.js +33 -0
  62. package/dist/delegation/storage/memory-statuslist-storage.js.map +1 -0
  63. package/dist/delegation/utils.d.ts +49 -0
  64. package/dist/delegation/utils.d.ts.map +1 -0
  65. package/dist/delegation/utils.js +131 -0
  66. package/dist/delegation/utils.js.map +1 -0
  67. package/dist/delegation/vc-issuer.d.ts +56 -0
  68. package/dist/delegation/vc-issuer.d.ts.map +1 -0
  69. package/dist/delegation/vc-issuer.js +80 -0
  70. package/dist/delegation/vc-issuer.js.map +1 -0
  71. package/dist/delegation/vc-verifier.d.ts +112 -0
  72. package/dist/delegation/vc-verifier.d.ts.map +1 -0
  73. package/dist/delegation/vc-verifier.js +280 -0
  74. package/dist/delegation/vc-verifier.js.map +1 -0
  75. package/dist/index.d.ts +45 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +53 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/logging/index.d.ts +2 -0
  80. package/dist/logging/index.d.ts.map +1 -0
  81. package/dist/logging/index.js +2 -0
  82. package/dist/logging/index.js.map +1 -0
  83. package/dist/logging/logger.d.ts +23 -0
  84. package/dist/logging/logger.d.ts.map +1 -0
  85. package/dist/logging/logger.js +82 -0
  86. package/dist/logging/logger.js.map +1 -0
  87. package/dist/middleware/index.d.ts +7 -0
  88. package/dist/middleware/index.d.ts.map +1 -0
  89. package/dist/middleware/index.js +7 -0
  90. package/dist/middleware/index.js.map +1 -0
  91. package/dist/middleware/with-mcpi.d.ts +152 -0
  92. package/dist/middleware/with-mcpi.d.ts.map +1 -0
  93. package/dist/middleware/with-mcpi.js +472 -0
  94. package/dist/middleware/with-mcpi.js.map +1 -0
  95. package/dist/proof/errors.d.ts +49 -0
  96. package/dist/proof/errors.d.ts.map +1 -0
  97. package/dist/proof/errors.js +61 -0
  98. package/dist/proof/errors.js.map +1 -0
  99. package/dist/proof/generator.d.ts +65 -0
  100. package/dist/proof/generator.d.ts.map +1 -0
  101. package/dist/proof/generator.js +163 -0
  102. package/dist/proof/generator.js.map +1 -0
  103. package/dist/proof/index.d.ts +4 -0
  104. package/dist/proof/index.d.ts.map +1 -0
  105. package/dist/proof/index.js +4 -0
  106. package/dist/proof/index.js.map +1 -0
  107. package/dist/proof/verifier.d.ts +108 -0
  108. package/dist/proof/verifier.d.ts.map +1 -0
  109. package/dist/proof/verifier.js +299 -0
  110. package/dist/proof/verifier.js.map +1 -0
  111. package/dist/providers/base.d.ts +64 -0
  112. package/dist/providers/base.d.ts.map +1 -0
  113. package/dist/providers/base.js +19 -0
  114. package/dist/providers/base.js.map +1 -0
  115. package/dist/providers/index.d.ts +3 -0
  116. package/dist/providers/index.d.ts.map +1 -0
  117. package/dist/providers/index.js +3 -0
  118. package/dist/providers/index.js.map +1 -0
  119. package/dist/providers/memory.d.ts +33 -0
  120. package/dist/providers/memory.d.ts.map +1 -0
  121. package/dist/providers/memory.js +102 -0
  122. package/dist/providers/memory.js.map +1 -0
  123. package/dist/session/index.d.ts +2 -0
  124. package/dist/session/index.d.ts.map +1 -0
  125. package/dist/session/index.js +2 -0
  126. package/dist/session/index.js.map +1 -0
  127. package/dist/session/manager.d.ts +77 -0
  128. package/dist/session/manager.d.ts.map +1 -0
  129. package/dist/session/manager.js +251 -0
  130. package/dist/session/manager.js.map +1 -0
  131. package/dist/types/protocol.d.ts +320 -0
  132. package/dist/types/protocol.d.ts.map +1 -0
  133. package/dist/types/protocol.js +229 -0
  134. package/dist/types/protocol.js.map +1 -0
  135. package/dist/utils/base58.d.ts +31 -0
  136. package/dist/utils/base58.d.ts.map +1 -0
  137. package/dist/utils/base58.js +104 -0
  138. package/dist/utils/base58.js.map +1 -0
  139. package/dist/utils/base64.d.ts +13 -0
  140. package/dist/utils/base64.d.ts.map +1 -0
  141. package/dist/utils/base64.js +99 -0
  142. package/dist/utils/base64.js.map +1 -0
  143. package/dist/utils/crypto-service.d.ts +37 -0
  144. package/dist/utils/crypto-service.d.ts.map +1 -0
  145. package/dist/utils/crypto-service.js +153 -0
  146. package/dist/utils/crypto-service.js.map +1 -0
  147. package/dist/utils/did-helpers.d.ts +156 -0
  148. package/dist/utils/did-helpers.d.ts.map +1 -0
  149. package/dist/utils/did-helpers.js +193 -0
  150. package/dist/utils/did-helpers.js.map +1 -0
  151. package/dist/utils/ed25519-constants.d.ts +18 -0
  152. package/dist/utils/ed25519-constants.d.ts.map +1 -0
  153. package/dist/utils/ed25519-constants.js +21 -0
  154. package/dist/utils/ed25519-constants.js.map +1 -0
  155. package/dist/utils/index.d.ts +5 -0
  156. package/dist/utils/index.d.ts.map +1 -0
  157. package/dist/utils/index.js +5 -0
  158. package/dist/utils/index.js.map +1 -0
  159. package/package.json +105 -0
  160. package/src/__tests__/integration/full-flow.test.ts +362 -0
  161. package/src/__tests__/providers/base.test.ts +173 -0
  162. package/src/__tests__/providers/memory.test.ts +332 -0
  163. package/src/__tests__/utils/mock-providers.ts +319 -0
  164. package/src/__tests__/utils/node-crypto-provider.ts +93 -0
  165. package/src/auth/handshake.ts +411 -0
  166. package/src/auth/index.ts +11 -0
  167. package/src/auth/types.ts +40 -0
  168. package/src/delegation/__tests__/audience-validator.test.ts +110 -0
  169. package/src/delegation/__tests__/bitstring.test.ts +346 -0
  170. package/src/delegation/__tests__/cascading-revocation.test.ts +624 -0
  171. package/src/delegation/__tests__/delegation-graph.test.ts +623 -0
  172. package/src/delegation/__tests__/did-key-resolver.test.ts +265 -0
  173. package/src/delegation/__tests__/did-web-resolver.test.ts +467 -0
  174. package/src/delegation/__tests__/outbound-headers.test.ts +230 -0
  175. package/src/delegation/__tests__/outbound-proof.test.ts +179 -0
  176. package/src/delegation/__tests__/statuslist-manager.test.ts +515 -0
  177. package/src/delegation/__tests__/utils.test.ts +185 -0
  178. package/src/delegation/__tests__/vc-issuer.test.ts +487 -0
  179. package/src/delegation/__tests__/vc-verifier.test.ts +1029 -0
  180. package/src/delegation/audience-validator.ts +24 -0
  181. package/src/delegation/bitstring.ts +160 -0
  182. package/src/delegation/cascading-revocation.ts +224 -0
  183. package/src/delegation/delegation-graph.ts +143 -0
  184. package/src/delegation/did-key-resolver.ts +181 -0
  185. package/src/delegation/did-web-resolver.ts +270 -0
  186. package/src/delegation/index.ts +33 -0
  187. package/src/delegation/outbound-headers.ts +193 -0
  188. package/src/delegation/outbound-proof.ts +90 -0
  189. package/src/delegation/statuslist-manager.ts +219 -0
  190. package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +366 -0
  191. package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +228 -0
  192. package/src/delegation/storage/memory-graph-storage.ts +178 -0
  193. package/src/delegation/storage/memory-statuslist-storage.ts +42 -0
  194. package/src/delegation/utils.ts +189 -0
  195. package/src/delegation/vc-issuer.ts +137 -0
  196. package/src/delegation/vc-verifier.ts +440 -0
  197. package/src/index.ts +264 -0
  198. package/src/logging/__tests__/logger.test.ts +366 -0
  199. package/src/logging/index.ts +6 -0
  200. package/src/logging/logger.ts +91 -0
  201. package/src/middleware/__tests__/with-mcpi.test.ts +504 -0
  202. package/src/middleware/index.ts +16 -0
  203. package/src/middleware/with-mcpi.ts +766 -0
  204. package/src/proof/__tests__/proof-generator.test.ts +483 -0
  205. package/src/proof/__tests__/verifier.test.ts +488 -0
  206. package/src/proof/errors.ts +75 -0
  207. package/src/proof/generator.ts +255 -0
  208. package/src/proof/index.ts +22 -0
  209. package/src/proof/verifier.ts +449 -0
  210. package/src/providers/base.ts +68 -0
  211. package/src/providers/index.ts +15 -0
  212. package/src/providers/memory.ts +130 -0
  213. package/src/session/__tests__/session-manager.test.ts +342 -0
  214. package/src/session/index.ts +7 -0
  215. package/src/session/manager.ts +332 -0
  216. package/src/types/protocol.ts +596 -0
  217. package/src/utils/__tests__/base58.test.ts +281 -0
  218. package/src/utils/__tests__/base64.test.ts +239 -0
  219. package/src/utils/__tests__/crypto-service.test.ts +530 -0
  220. package/src/utils/__tests__/did-helpers.test.ts +156 -0
  221. package/src/utils/base58.ts +115 -0
  222. package/src/utils/base64.ts +116 -0
  223. package/src/utils/crypto-service.ts +209 -0
  224. package/src/utils/did-helpers.ts +210 -0
  225. package/src/utils/ed25519-constants.ts +23 -0
  226. package/src/utils/index.ts +9 -0
@@ -0,0 +1,228 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { MemoryStatusListStorage } from "../memory-statuslist-storage.js";
3
+ import type { StatusList2021Credential } from "../../../types/protocol.js";
4
+
5
+ describe("MemoryStatusListStorage", () => {
6
+ let storage: MemoryStatusListStorage;
7
+
8
+ const mockStatusListCredential: StatusList2021Credential = {
9
+ "@context": [
10
+ "https://www.w3.org/2018/credentials/v1",
11
+ "https://w3id.org/vc/status-list/2021/v1",
12
+ ],
13
+ id: "https://example.com/status/revocation/v1",
14
+ type: ["VerifiableCredential", "StatusList2021Credential"],
15
+ issuer: "did:web:example.com",
16
+ issuanceDate: "2024-01-01T00:00:00Z",
17
+ credentialSubject: {
18
+ id: "https://example.com/status/revocation/v1#list",
19
+ type: "StatusList2021",
20
+ statusPurpose: "revocation",
21
+ encodedList: "H4sIAAAAAAAAA2NgGAWjYBSMglEwCkYBqwAA0kEQVAEAAA==",
22
+ },
23
+ proof: {
24
+ type: "Ed25519Signature2020",
25
+ created: "2024-01-01T00:00:00Z",
26
+ verificationMethod: "did:web:example.com#key-1",
27
+ proofPurpose: "assertionMethod",
28
+ proofValue: "mock-proof",
29
+ },
30
+ };
31
+
32
+ beforeEach(() => {
33
+ storage = new MemoryStatusListStorage();
34
+ });
35
+
36
+ describe("getStatusList", () => {
37
+ it("should return null for non-existent status list", async () => {
38
+ const result = await storage.getStatusList("non-existent");
39
+ expect(result).toBeNull();
40
+ });
41
+
42
+ it("should return stored status list", async () => {
43
+ await storage.setStatusList(
44
+ "https://example.com/status/revocation/v1",
45
+ mockStatusListCredential
46
+ );
47
+
48
+ const result = await storage.getStatusList(
49
+ "https://example.com/status/revocation/v1"
50
+ );
51
+
52
+ expect(result).toEqual(mockStatusListCredential);
53
+ });
54
+ });
55
+
56
+ describe("setStatusList", () => {
57
+ it("should store status list", async () => {
58
+ await storage.setStatusList(
59
+ "https://example.com/status/revocation/v1",
60
+ mockStatusListCredential
61
+ );
62
+
63
+ const result = await storage.getStatusList(
64
+ "https://example.com/status/revocation/v1"
65
+ );
66
+ expect(result).toEqual(mockStatusListCredential);
67
+ });
68
+
69
+ it("should overwrite existing status list", async () => {
70
+ await storage.setStatusList(
71
+ "https://example.com/status/revocation/v1",
72
+ mockStatusListCredential
73
+ );
74
+
75
+ const updatedCredential = {
76
+ ...mockStatusListCredential,
77
+ issuanceDate: "2024-01-02T00:00:00Z",
78
+ };
79
+
80
+ await storage.setStatusList(
81
+ "https://example.com/status/revocation/v1",
82
+ updatedCredential
83
+ );
84
+
85
+ const result = await storage.getStatusList(
86
+ "https://example.com/status/revocation/v1"
87
+ );
88
+ expect(result?.issuanceDate).toBe("2024-01-02T00:00:00Z");
89
+ });
90
+
91
+ it("should store multiple status lists independently", async () => {
92
+ const revocationCredential = mockStatusListCredential;
93
+ const suspensionCredential = {
94
+ ...mockStatusListCredential,
95
+ id: "https://example.com/status/suspension/v1",
96
+ credentialSubject: {
97
+ ...mockStatusListCredential.credentialSubject,
98
+ id: "https://example.com/status/suspension/v1#list",
99
+ statusPurpose: "suspension" as const,
100
+ },
101
+ };
102
+
103
+ await storage.setStatusList(
104
+ "https://example.com/status/revocation/v1",
105
+ revocationCredential
106
+ );
107
+ await storage.setStatusList(
108
+ "https://example.com/status/suspension/v1",
109
+ suspensionCredential
110
+ );
111
+
112
+ const revocation = await storage.getStatusList(
113
+ "https://example.com/status/revocation/v1"
114
+ );
115
+ const suspension = await storage.getStatusList(
116
+ "https://example.com/status/suspension/v1"
117
+ );
118
+
119
+ expect(revocation?.credentialSubject.statusPurpose).toBe("revocation");
120
+ expect(suspension?.credentialSubject.statusPurpose).toBe("suspension");
121
+ });
122
+ });
123
+
124
+ describe("allocateIndex", () => {
125
+ it("should allocate sequential indices starting from 0", async () => {
126
+ const statusListId = "https://example.com/status/revocation/v1";
127
+
128
+ const index1 = await storage.allocateIndex(statusListId);
129
+ const index2 = await storage.allocateIndex(statusListId);
130
+ const index3 = await storage.allocateIndex(statusListId);
131
+
132
+ expect(index1).toBe(0);
133
+ expect(index2).toBe(1);
134
+ expect(index3).toBe(2);
135
+ });
136
+
137
+ it("should allocate indices independently per status list", async () => {
138
+ const list1 = "https://example.com/status/revocation/v1";
139
+ const list2 = "https://example.com/status/suspension/v1";
140
+
141
+ const index1 = await storage.allocateIndex(list1);
142
+ const index2 = await storage.allocateIndex(list2);
143
+ const index3 = await storage.allocateIndex(list1);
144
+
145
+ expect(index1).toBe(0);
146
+ expect(index2).toBe(0); // Independent counter
147
+ expect(index3).toBe(1);
148
+ });
149
+
150
+ it("should continue from previous allocation after clear", async () => {
151
+ const statusListId = "https://example.com/status/revocation/v1";
152
+
153
+ await storage.allocateIndex(statusListId);
154
+ await storage.allocateIndex(statusListId);
155
+ storage.clear();
156
+
157
+ const index = await storage.allocateIndex(statusListId);
158
+ expect(index).toBe(0); // Starts fresh after clear
159
+ });
160
+ });
161
+
162
+ describe("getIndexCount", () => {
163
+ it("should return 0 for new status list", () => {
164
+ const count = storage.getIndexCount("https://example.com/status/new");
165
+ expect(count).toBe(0);
166
+ });
167
+
168
+ it("should return current allocation count", async () => {
169
+ const statusListId = "https://example.com/status/revocation/v1";
170
+
171
+ await storage.allocateIndex(statusListId);
172
+ expect(storage.getIndexCount(statusListId)).toBe(1);
173
+
174
+ await storage.allocateIndex(statusListId);
175
+ expect(storage.getIndexCount(statusListId)).toBe(2);
176
+ });
177
+ });
178
+
179
+ describe("clear", () => {
180
+ it("should remove all status lists", async () => {
181
+ await storage.setStatusList(
182
+ "https://example.com/status/revocation/v1",
183
+ mockStatusListCredential
184
+ );
185
+
186
+ storage.clear();
187
+
188
+ const result = await storage.getStatusList(
189
+ "https://example.com/status/revocation/v1"
190
+ );
191
+ expect(result).toBeNull();
192
+ });
193
+
194
+ it("should reset index counters", async () => {
195
+ const statusListId = "https://example.com/status/revocation/v1";
196
+
197
+ await storage.allocateIndex(statusListId);
198
+ await storage.allocateIndex(statusListId);
199
+
200
+ storage.clear();
201
+
202
+ expect(storage.getIndexCount(statusListId)).toBe(0);
203
+ });
204
+ });
205
+
206
+ describe("getAllStatusListIds", () => {
207
+ it("should return empty array when no status lists", () => {
208
+ const ids = storage.getAllStatusListIds();
209
+ expect(ids).toEqual([]);
210
+ });
211
+
212
+ it("should return all stored status list IDs", async () => {
213
+ await storage.setStatusList(
214
+ "https://example.com/status/revocation/v1",
215
+ mockStatusListCredential
216
+ );
217
+ await storage.setStatusList("https://example.com/status/suspension/v1", {
218
+ ...mockStatusListCredential,
219
+ id: "https://example.com/status/suspension/v1",
220
+ });
221
+
222
+ const ids = storage.getAllStatusListIds();
223
+ expect(ids).toContain("https://example.com/status/revocation/v1");
224
+ expect(ids).toContain("https://example.com/status/suspension/v1");
225
+ expect(ids.length).toBe(2);
226
+ });
227
+ });
228
+ });
@@ -0,0 +1,178 @@
1
+ /**
2
+ * In-Memory Delegation Graph Storage Provider
3
+ *
4
+ * Memory-based implementation for testing and development.
5
+ * NOT suitable for production (no persistence).
6
+ *
7
+ * SOLID: Implements DelegationGraphStorageProvider interface
8
+ */
9
+
10
+ import type {
11
+ DelegationGraphStorageProvider,
12
+ DelegationNode,
13
+ } from '../delegation-graph.js';
14
+
15
+ /**
16
+ * Memory-based Delegation Graph storage
17
+ *
18
+ * Stores delegation nodes in memory with efficient graph queries.
19
+ * Useful for:
20
+ * - Unit tests
21
+ * - Integration tests
22
+ * - Development/debugging
23
+ * - Examples
24
+ */
25
+ export class MemoryDelegationGraphStorage
26
+ implements DelegationGraphStorageProvider
27
+ {
28
+ private nodes = new Map<string, DelegationNode>();
29
+
30
+ /**
31
+ * Get a delegation node by ID
32
+ */
33
+ async getNode(delegationId: string): Promise<DelegationNode | null> {
34
+ return this.nodes.get(delegationId) || null;
35
+ }
36
+
37
+ /**
38
+ * Save a delegation node
39
+ */
40
+ async setNode(node: DelegationNode): Promise<void> {
41
+ this.nodes.set(node.id, node);
42
+ }
43
+
44
+ /**
45
+ * Get all children of a delegation
46
+ */
47
+ async getChildren(delegationId: string): Promise<DelegationNode[]> {
48
+ const parent = this.nodes.get(delegationId);
49
+ if (!parent) return [];
50
+
51
+ return parent.children
52
+ .map((childId) => this.nodes.get(childId))
53
+ .filter((node): node is DelegationNode => node !== undefined);
54
+ }
55
+
56
+ /**
57
+ * Get the full chain from root to this delegation
58
+ */
59
+ async getChain(delegationId: string): Promise<DelegationNode[]> {
60
+ const chain: DelegationNode[] = [];
61
+ let currentId: string | null = delegationId;
62
+
63
+ // Walk up the tree to root
64
+ while (currentId) {
65
+ const node = this.nodes.get(currentId);
66
+ if (!node) break;
67
+
68
+ chain.unshift(node); // Add to front (root first)
69
+ currentId = node.parentId;
70
+ }
71
+
72
+ return chain;
73
+ }
74
+
75
+ /**
76
+ * Get all descendants (children, grandchildren, etc.)
77
+ *
78
+ * Uses BFS for efficiency.
79
+ */
80
+ async getDescendants(delegationId: string): Promise<DelegationNode[]> {
81
+ const descendants: DelegationNode[] = [];
82
+ const queue: string[] = [delegationId];
83
+ const visited = new Set<string>();
84
+
85
+ while (queue.length > 0) {
86
+ const currentId = queue.shift()!;
87
+
88
+ // Skip if already visited (prevent infinite loops)
89
+ if (visited.has(currentId)) continue;
90
+ visited.add(currentId);
91
+
92
+ const node = this.nodes.get(currentId);
93
+ if (!node) continue;
94
+
95
+ // Add children to queue
96
+ for (const childId of node.children) {
97
+ if (!visited.has(childId)) {
98
+ queue.push(childId);
99
+
100
+ const childNode = this.nodes.get(childId);
101
+ if (childNode) {
102
+ descendants.push(childNode);
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ return descendants;
109
+ }
110
+
111
+ /**
112
+ * Delete a node
113
+ */
114
+ async deleteNode(delegationId: string): Promise<void> {
115
+ this.nodes.delete(delegationId);
116
+ }
117
+
118
+ /**
119
+ * Clear all data (for testing)
120
+ */
121
+ clear(): void {
122
+ this.nodes.clear();
123
+ }
124
+
125
+ /**
126
+ * Get all node IDs (for testing)
127
+ */
128
+ getAllNodeIds(): string[] {
129
+ return Array.from(this.nodes.keys());
130
+ }
131
+
132
+ /**
133
+ * Get graph statistics (for testing/debugging)
134
+ */
135
+ getStats(): {
136
+ totalNodes: number;
137
+ rootNodes: number;
138
+ leafNodes: number;
139
+ maxDepth: number;
140
+ } {
141
+ const nodes = Array.from(this.nodes.values());
142
+
143
+ const rootNodes = nodes.filter((n) => n.parentId === null).length;
144
+ const leafNodes = nodes.filter((n) => n.children.length === 0).length;
145
+
146
+ // Calculate max depth
147
+ let maxDepth = 0;
148
+ for (const node of nodes) {
149
+ const chain = this.getChainSync(node.id);
150
+ maxDepth = Math.max(maxDepth, chain.length - 1);
151
+ }
152
+
153
+ return {
154
+ totalNodes: nodes.length,
155
+ rootNodes,
156
+ leafNodes,
157
+ maxDepth,
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Synchronous chain retrieval (for stats)
163
+ */
164
+ private getChainSync(delegationId: string): DelegationNode[] {
165
+ const chain: DelegationNode[] = [];
166
+ let currentId: string | null = delegationId;
167
+
168
+ while (currentId) {
169
+ const node = this.nodes.get(currentId);
170
+ if (!node) break;
171
+
172
+ chain.unshift(node);
173
+ currentId = node.parentId;
174
+ }
175
+
176
+ return chain;
177
+ }
178
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * In-Memory StatusList Storage Provider
3
+ *
4
+ * Memory-based implementation for testing and development.
5
+ * NOT suitable for production (no persistence).
6
+ */
7
+
8
+ import type { StatusList2021Credential } from '../../types/protocol.js';
9
+ import type { StatusListStorageProvider } from '../statuslist-manager.js';
10
+
11
+ export class MemoryStatusListStorage implements StatusListStorageProvider {
12
+ private statusLists = new Map<string, StatusList2021Credential>();
13
+ private indexCounters = new Map<string, number>();
14
+
15
+ async getStatusList(statusListId: string): Promise<StatusList2021Credential | null> {
16
+ return this.statusLists.get(statusListId) || null;
17
+ }
18
+
19
+ async setStatusList(statusListId: string, credential: StatusList2021Credential): Promise<void> {
20
+ this.statusLists.set(statusListId, credential);
21
+ }
22
+
23
+ async allocateIndex(statusListId: string): Promise<number> {
24
+ const current = this.indexCounters.get(statusListId) || 0;
25
+ const allocated = current;
26
+ this.indexCounters.set(statusListId, current + 1);
27
+ return allocated;
28
+ }
29
+
30
+ getIndexCount(statusListId: string): number {
31
+ return this.indexCounters.get(statusListId) || 0;
32
+ }
33
+
34
+ clear(): void {
35
+ this.statusLists.clear();
36
+ this.indexCounters.clear();
37
+ }
38
+
39
+ getAllStatusListIds(): string[] {
40
+ return Array.from(this.statusLists.keys());
41
+ }
42
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Delegation Utilities
3
+ *
4
+ * Shared utility functions for delegation credential operations.
5
+ */
6
+
7
+ import { base64urlEncodeFromString, base64urlDecodeToString } from '../utils/base64.js';
8
+ import { canonicalize } from 'json-canonicalize';
9
+
10
+ /**
11
+ * Canonicalize a JSON value using RFC 8785 (JSON Canonicalization Scheme).
12
+ *
13
+ * Wraps `json-canonicalize` with input validation to reject values that are
14
+ * not representable in JSON (Infinity, NaN, undefined, functions, symbols,
15
+ * bigints). The underlying library silently coerces these to `"null"` or
16
+ * `"undefined"`, which is dangerous for cryptographic canonicalization where
17
+ * distinct inputs must produce distinct outputs.
18
+ *
19
+ * @throws {TypeError} If `obj` is not a valid JSON value
20
+ */
21
+ export function canonicalizeJSON(obj: unknown): string {
22
+ assertJsonSafe(obj);
23
+ return canonicalize(obj);
24
+ }
25
+
26
+ /**
27
+ * Recursively validates that a value is JSON-safe.
28
+ * Throws TypeError for Infinity, NaN, undefined, functions, symbols, bigints.
29
+ */
30
+ function assertJsonSafe(value: unknown, path = '$'): void {
31
+ if (value === null || typeof value === 'boolean' || typeof value === 'string') {
32
+ return;
33
+ }
34
+
35
+ if (typeof value === 'number') {
36
+ if (!Number.isFinite(value)) {
37
+ throw new TypeError(
38
+ `Cannot canonicalize non-finite number at ${path}: ${value}`
39
+ );
40
+ }
41
+ return;
42
+ }
43
+
44
+ if (typeof value === 'undefined') {
45
+ throw new TypeError(`Cannot canonicalize undefined at ${path}`);
46
+ }
47
+
48
+ if (typeof value === 'function') {
49
+ throw new TypeError(`Cannot canonicalize function at ${path}`);
50
+ }
51
+
52
+ if (typeof value === 'symbol') {
53
+ throw new TypeError(`Cannot canonicalize symbol at ${path}`);
54
+ }
55
+
56
+ if (typeof value === 'bigint') {
57
+ throw new TypeError(`Cannot canonicalize bigint at ${path}`);
58
+ }
59
+
60
+ if (Array.isArray(value)) {
61
+ for (let i = 0; i < value.length; i++) {
62
+ assertJsonSafe(value[i], `${path}[${i}]`);
63
+ }
64
+ return;
65
+ }
66
+
67
+ if (typeof value === 'object') {
68
+ for (const [key, val] of Object.entries(value as Record<string, unknown>)) {
69
+ assertJsonSafe(val, `${path}.${key}`);
70
+ }
71
+ return;
72
+ }
73
+ }
74
+
75
+ export interface VCJWTHeader {
76
+ alg: 'EdDSA';
77
+ typ: 'JWT' | 'vc+jwt';
78
+ kid?: string;
79
+ }
80
+
81
+ export interface VCJWTPayload {
82
+ iss: string;
83
+ sub?: string;
84
+ aud?: string | string[];
85
+ exp?: number;
86
+ iat?: number;
87
+ jti?: string;
88
+ vc: Record<string, unknown>;
89
+ }
90
+
91
+ export interface EncodeVCAsJWTOptions {
92
+ keyId?: string;
93
+ }
94
+
95
+ export function createUnsignedVCJWT(
96
+ vc: Record<string, unknown>,
97
+ options: EncodeVCAsJWTOptions = {}
98
+ ): {
99
+ header: VCJWTHeader;
100
+ payload: VCJWTPayload;
101
+ encodedHeader: string;
102
+ encodedPayload: string;
103
+ signingInput: string;
104
+ } {
105
+ const header: VCJWTHeader = {
106
+ alg: 'EdDSA',
107
+ typ: 'JWT',
108
+ };
109
+ if (options.keyId) {
110
+ header.kid = options.keyId;
111
+ }
112
+
113
+ const issuer =
114
+ typeof vc['issuer'] === 'string'
115
+ ? vc['issuer']
116
+ : ((vc['issuer'] as Record<string, unknown>)?.['id'] as string);
117
+ const subject = (vc['credentialSubject'] as Record<string, unknown>)?.['id'] as
118
+ | string
119
+ | undefined;
120
+
121
+ let exp: number | undefined;
122
+ let iat: number | undefined;
123
+
124
+ if (vc['expirationDate'] && typeof vc['expirationDate'] === 'string') {
125
+ exp = Math.floor(new Date(vc['expirationDate']).getTime() / 1000);
126
+ }
127
+ if (vc['issuanceDate'] && typeof vc['issuanceDate'] === 'string') {
128
+ iat = Math.floor(new Date(vc['issuanceDate']).getTime() / 1000);
129
+ }
130
+
131
+ const vcWithoutProof = { ...vc };
132
+ delete vcWithoutProof['proof'];
133
+
134
+ const payload: VCJWTPayload = {
135
+ iss: issuer,
136
+ vc: vcWithoutProof,
137
+ };
138
+
139
+ if (subject) payload.sub = subject;
140
+ if (exp) payload.exp = exp;
141
+ if (iat) payload.iat = iat;
142
+ if (vc['id'] && typeof vc['id'] === 'string') payload.jti = vc['id'];
143
+
144
+ const encodedHeader = base64urlEncodeFromString(JSON.stringify(header));
145
+ const encodedPayload = base64urlEncodeFromString(JSON.stringify(payload));
146
+ const signingInput = `${encodedHeader}.${encodedPayload}`;
147
+
148
+ return {
149
+ header,
150
+ payload,
151
+ encodedHeader,
152
+ encodedPayload,
153
+ signingInput,
154
+ };
155
+ }
156
+
157
+ export function completeVCJWT(signingInput: string, signature: string): string {
158
+ return `${signingInput}.${signature}`;
159
+ }
160
+
161
+ export function parseVCJWT(jwt: string): {
162
+ header: VCJWTHeader;
163
+ payload: VCJWTPayload;
164
+ signature: string;
165
+ signingInput: string;
166
+ } | null {
167
+ const parts = jwt.split('.');
168
+ if (parts.length !== 3) {
169
+ return null;
170
+ }
171
+
172
+ try {
173
+ const headerJson = base64urlDecodeToString(parts[0]!);
174
+ const payloadJson = base64urlDecodeToString(parts[1]!);
175
+
176
+ const header = JSON.parse(headerJson) as VCJWTHeader;
177
+ const payload = JSON.parse(payloadJson) as VCJWTPayload;
178
+
179
+ return {
180
+ header,
181
+ payload,
182
+ signature: parts[2]!,
183
+ signingInput: `${parts[0]}.${parts[1]}`,
184
+ };
185
+ } catch {
186
+ return null;
187
+ }
188
+ }
189
+