@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.
- package/LICENSE +21 -0
- package/README.md +390 -0
- package/dist/auth/handshake.d.ts +104 -0
- package/dist/auth/handshake.d.ts.map +1 -0
- package/dist/auth/handshake.js +230 -0
- package/dist/auth/handshake.js.map +1 -0
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/types.d.ts +31 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +7 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/delegation/audience-validator.d.ts +9 -0
- package/dist/delegation/audience-validator.d.ts.map +1 -0
- package/dist/delegation/audience-validator.js +17 -0
- package/dist/delegation/audience-validator.js.map +1 -0
- package/dist/delegation/bitstring.d.ts +37 -0
- package/dist/delegation/bitstring.d.ts.map +1 -0
- package/dist/delegation/bitstring.js +117 -0
- package/dist/delegation/bitstring.js.map +1 -0
- package/dist/delegation/cascading-revocation.d.ts +45 -0
- package/dist/delegation/cascading-revocation.d.ts.map +1 -0
- package/dist/delegation/cascading-revocation.js +148 -0
- package/dist/delegation/cascading-revocation.js.map +1 -0
- package/dist/delegation/delegation-graph.d.ts +49 -0
- package/dist/delegation/delegation-graph.d.ts.map +1 -0
- package/dist/delegation/delegation-graph.js +99 -0
- package/dist/delegation/delegation-graph.js.map +1 -0
- package/dist/delegation/did-key-resolver.d.ts +64 -0
- package/dist/delegation/did-key-resolver.d.ts.map +1 -0
- package/dist/delegation/did-key-resolver.js +154 -0
- package/dist/delegation/did-key-resolver.js.map +1 -0
- package/dist/delegation/did-web-resolver.d.ts +83 -0
- package/dist/delegation/did-web-resolver.d.ts.map +1 -0
- package/dist/delegation/did-web-resolver.js +218 -0
- package/dist/delegation/did-web-resolver.js.map +1 -0
- package/dist/delegation/index.d.ts +21 -0
- package/dist/delegation/index.d.ts.map +1 -0
- package/dist/delegation/index.js +21 -0
- package/dist/delegation/index.js.map +1 -0
- package/dist/delegation/outbound-headers.d.ts +81 -0
- package/dist/delegation/outbound-headers.d.ts.map +1 -0
- package/dist/delegation/outbound-headers.js +139 -0
- package/dist/delegation/outbound-headers.js.map +1 -0
- package/dist/delegation/outbound-proof.d.ts +43 -0
- package/dist/delegation/outbound-proof.d.ts.map +1 -0
- package/dist/delegation/outbound-proof.js +52 -0
- package/dist/delegation/outbound-proof.js.map +1 -0
- package/dist/delegation/statuslist-manager.d.ts +44 -0
- package/dist/delegation/statuslist-manager.d.ts.map +1 -0
- package/dist/delegation/statuslist-manager.js +126 -0
- package/dist/delegation/statuslist-manager.js.map +1 -0
- package/dist/delegation/storage/memory-graph-storage.d.ts +70 -0
- package/dist/delegation/storage/memory-graph-storage.d.ts.map +1 -0
- package/dist/delegation/storage/memory-graph-storage.js +145 -0
- package/dist/delegation/storage/memory-graph-storage.js.map +1 -0
- package/dist/delegation/storage/memory-statuslist-storage.d.ts +19 -0
- package/dist/delegation/storage/memory-statuslist-storage.d.ts.map +1 -0
- package/dist/delegation/storage/memory-statuslist-storage.js +33 -0
- package/dist/delegation/storage/memory-statuslist-storage.js.map +1 -0
- package/dist/delegation/utils.d.ts +49 -0
- package/dist/delegation/utils.d.ts.map +1 -0
- package/dist/delegation/utils.js +131 -0
- package/dist/delegation/utils.js.map +1 -0
- package/dist/delegation/vc-issuer.d.ts +56 -0
- package/dist/delegation/vc-issuer.d.ts.map +1 -0
- package/dist/delegation/vc-issuer.js +80 -0
- package/dist/delegation/vc-issuer.js.map +1 -0
- package/dist/delegation/vc-verifier.d.ts +112 -0
- package/dist/delegation/vc-verifier.d.ts.map +1 -0
- package/dist/delegation/vc-verifier.js +280 -0
- package/dist/delegation/vc-verifier.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/index.d.ts +2 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +2 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.d.ts +23 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +82 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/middleware/index.d.ts +7 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +7 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/with-mcpi.d.ts +152 -0
- package/dist/middleware/with-mcpi.d.ts.map +1 -0
- package/dist/middleware/with-mcpi.js +472 -0
- package/dist/middleware/with-mcpi.js.map +1 -0
- package/dist/proof/errors.d.ts +49 -0
- package/dist/proof/errors.d.ts.map +1 -0
- package/dist/proof/errors.js +61 -0
- package/dist/proof/errors.js.map +1 -0
- package/dist/proof/generator.d.ts +65 -0
- package/dist/proof/generator.d.ts.map +1 -0
- package/dist/proof/generator.js +163 -0
- package/dist/proof/generator.js.map +1 -0
- package/dist/proof/index.d.ts +4 -0
- package/dist/proof/index.d.ts.map +1 -0
- package/dist/proof/index.js +4 -0
- package/dist/proof/index.js.map +1 -0
- package/dist/proof/verifier.d.ts +108 -0
- package/dist/proof/verifier.d.ts.map +1 -0
- package/dist/proof/verifier.js +299 -0
- package/dist/proof/verifier.js.map +1 -0
- package/dist/providers/base.d.ts +64 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +19 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +3 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/memory.d.ts +33 -0
- package/dist/providers/memory.d.ts.map +1 -0
- package/dist/providers/memory.js +102 -0
- package/dist/providers/memory.js.map +1 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +2 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/manager.d.ts +77 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +251 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/types/protocol.d.ts +320 -0
- package/dist/types/protocol.d.ts.map +1 -0
- package/dist/types/protocol.js +229 -0
- package/dist/types/protocol.js.map +1 -0
- package/dist/utils/base58.d.ts +31 -0
- package/dist/utils/base58.d.ts.map +1 -0
- package/dist/utils/base58.js +104 -0
- package/dist/utils/base58.js.map +1 -0
- package/dist/utils/base64.d.ts +13 -0
- package/dist/utils/base64.d.ts.map +1 -0
- package/dist/utils/base64.js +99 -0
- package/dist/utils/base64.js.map +1 -0
- package/dist/utils/crypto-service.d.ts +37 -0
- package/dist/utils/crypto-service.d.ts.map +1 -0
- package/dist/utils/crypto-service.js +153 -0
- package/dist/utils/crypto-service.js.map +1 -0
- package/dist/utils/did-helpers.d.ts +156 -0
- package/dist/utils/did-helpers.d.ts.map +1 -0
- package/dist/utils/did-helpers.js +193 -0
- package/dist/utils/did-helpers.js.map +1 -0
- package/dist/utils/ed25519-constants.d.ts +18 -0
- package/dist/utils/ed25519-constants.d.ts.map +1 -0
- package/dist/utils/ed25519-constants.js +21 -0
- package/dist/utils/ed25519-constants.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +105 -0
- package/src/__tests__/integration/full-flow.test.ts +362 -0
- package/src/__tests__/providers/base.test.ts +173 -0
- package/src/__tests__/providers/memory.test.ts +332 -0
- package/src/__tests__/utils/mock-providers.ts +319 -0
- package/src/__tests__/utils/node-crypto-provider.ts +93 -0
- package/src/auth/handshake.ts +411 -0
- package/src/auth/index.ts +11 -0
- package/src/auth/types.ts +40 -0
- package/src/delegation/__tests__/audience-validator.test.ts +110 -0
- package/src/delegation/__tests__/bitstring.test.ts +346 -0
- package/src/delegation/__tests__/cascading-revocation.test.ts +624 -0
- package/src/delegation/__tests__/delegation-graph.test.ts +623 -0
- package/src/delegation/__tests__/did-key-resolver.test.ts +265 -0
- package/src/delegation/__tests__/did-web-resolver.test.ts +467 -0
- package/src/delegation/__tests__/outbound-headers.test.ts +230 -0
- package/src/delegation/__tests__/outbound-proof.test.ts +179 -0
- package/src/delegation/__tests__/statuslist-manager.test.ts +515 -0
- package/src/delegation/__tests__/utils.test.ts +185 -0
- package/src/delegation/__tests__/vc-issuer.test.ts +487 -0
- package/src/delegation/__tests__/vc-verifier.test.ts +1029 -0
- package/src/delegation/audience-validator.ts +24 -0
- package/src/delegation/bitstring.ts +160 -0
- package/src/delegation/cascading-revocation.ts +224 -0
- package/src/delegation/delegation-graph.ts +143 -0
- package/src/delegation/did-key-resolver.ts +181 -0
- package/src/delegation/did-web-resolver.ts +270 -0
- package/src/delegation/index.ts +33 -0
- package/src/delegation/outbound-headers.ts +193 -0
- package/src/delegation/outbound-proof.ts +90 -0
- package/src/delegation/statuslist-manager.ts +219 -0
- package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +366 -0
- package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +228 -0
- package/src/delegation/storage/memory-graph-storage.ts +178 -0
- package/src/delegation/storage/memory-statuslist-storage.ts +42 -0
- package/src/delegation/utils.ts +189 -0
- package/src/delegation/vc-issuer.ts +137 -0
- package/src/delegation/vc-verifier.ts +440 -0
- package/src/index.ts +264 -0
- package/src/logging/__tests__/logger.test.ts +366 -0
- package/src/logging/index.ts +6 -0
- package/src/logging/logger.ts +91 -0
- package/src/middleware/__tests__/with-mcpi.test.ts +504 -0
- package/src/middleware/index.ts +16 -0
- package/src/middleware/with-mcpi.ts +766 -0
- package/src/proof/__tests__/proof-generator.test.ts +483 -0
- package/src/proof/__tests__/verifier.test.ts +488 -0
- package/src/proof/errors.ts +75 -0
- package/src/proof/generator.ts +255 -0
- package/src/proof/index.ts +22 -0
- package/src/proof/verifier.ts +449 -0
- package/src/providers/base.ts +68 -0
- package/src/providers/index.ts +15 -0
- package/src/providers/memory.ts +130 -0
- package/src/session/__tests__/session-manager.test.ts +342 -0
- package/src/session/index.ts +7 -0
- package/src/session/manager.ts +332 -0
- package/src/types/protocol.ts +596 -0
- package/src/utils/__tests__/base58.test.ts +281 -0
- package/src/utils/__tests__/base64.test.ts +239 -0
- package/src/utils/__tests__/crypto-service.test.ts +530 -0
- package/src/utils/__tests__/did-helpers.test.ts +156 -0
- package/src/utils/base58.ts +115 -0
- package/src/utils/base64.ts +116 -0
- package/src/utils/crypto-service.ts +209 -0
- package/src/utils/did-helpers.ts +210 -0
- package/src/utils/ed25519-constants.ts +23 -0
- 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
|
+
|