@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
package/package.json ADDED
@@ -0,0 +1,105 @@
1
+ {
2
+ "name": "@mcp-i/core",
3
+ "version": "0.1.0",
4
+ "description": "Core library for MCP-I — delegation, proof, and session primitives for Model Context Protocol Identity",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./delegation": {
15
+ "types": "./dist/delegation/index.d.ts",
16
+ "default": "./dist/delegation/index.js"
17
+ },
18
+ "./auth": {
19
+ "types": "./dist/auth/index.d.ts",
20
+ "default": "./dist/auth/index.js"
21
+ },
22
+ "./proof": {
23
+ "types": "./dist/proof/index.d.ts",
24
+ "default": "./dist/proof/index.js"
25
+ },
26
+ "./session": {
27
+ "types": "./dist/session/index.d.ts",
28
+ "default": "./dist/session/index.js"
29
+ },
30
+ "./providers": {
31
+ "types": "./dist/providers/index.d.ts",
32
+ "default": "./dist/providers/index.js"
33
+ },
34
+ "./logging": {
35
+ "types": "./dist/logging/index.d.ts",
36
+ "default": "./dist/logging/index.js"
37
+ },
38
+ "./types": {
39
+ "types": "./dist/types/protocol.d.ts",
40
+ "default": "./dist/types/protocol.js"
41
+ },
42
+ "./middleware": {
43
+ "types": "./dist/middleware/index.d.ts",
44
+ "default": "./dist/middleware/index.js"
45
+ }
46
+ },
47
+ "files": [
48
+ "dist",
49
+ "src",
50
+ "LICENSE",
51
+ "README.md"
52
+ ],
53
+ "scripts": {
54
+ "build": "tsc",
55
+ "test": "vitest run",
56
+ "test:watch": "vitest",
57
+ "test:coverage": "vitest run --coverage",
58
+ "typecheck": "tsc --noEmit",
59
+ "lint": "eslint src --ext .ts",
60
+ "clean": "rm -rf dist",
61
+ "example:server": "npx tsx examples/node-server/server.ts",
62
+ "example:inspector": "npx @modelcontextprotocol/inspector npx tsx examples/node-server/server.ts --stdio"
63
+ },
64
+ "dependencies": {
65
+ "jose": "^5.6.3",
66
+ "json-canonicalize": "^2.0.0"
67
+ },
68
+ "peerDependencies": {
69
+ "@modelcontextprotocol/sdk": ">=1.0.0"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "@modelcontextprotocol/sdk": {
73
+ "optional": true
74
+ }
75
+ },
76
+ "devDependencies": {
77
+ "@eslint/js": "^10.0.1",
78
+ "@modelcontextprotocol/sdk": "^1.12.1",
79
+ "@types/node": "^20.14.9",
80
+ "@typescript-eslint/eslint-plugin": "^8.57.0",
81
+ "@typescript-eslint/parser": "^8.57.0",
82
+ "@vitest/coverage-v8": "^2.0.0",
83
+ "eslint": "^10.0.3",
84
+ "typescript": "^5.5.3",
85
+ "typescript-eslint": "^8.57.0",
86
+ "vitest": "^2.0.0"
87
+ },
88
+ "keywords": [
89
+ "mcp-i",
90
+ "mcp",
91
+ "delegation",
92
+ "verifiable-credentials",
93
+ "did",
94
+ "protocol",
95
+ "identity",
96
+ "dif"
97
+ ],
98
+ "repository": {
99
+ "type": "git",
100
+ "url": "https://github.com/modelcontextprotocol-identity/mcp-i-core"
101
+ },
102
+ "publishConfig": {
103
+ "access": "public"
104
+ }
105
+ }
@@ -0,0 +1,362 @@
1
+ /**
2
+ * End-to-End Integration Test
3
+ *
4
+ * Exercises the full MCP-I protocol flow with real Ed25519 cryptography:
5
+ * handshake → session → tool call → proof generation → proof verification
6
+ *
7
+ * No mocks — uses NodeCryptoProvider for real signing and verification.
8
+ */
9
+
10
+ import { describe, it, expect } from "vitest";
11
+ import { NodeCryptoProvider } from "../utils/node-crypto-provider.js";
12
+ import { MemoryIdentityProvider } from "../../providers/memory.js";
13
+ import { MemoryNonceCacheProvider } from "../../providers/memory.js";
14
+ import { SessionManager, createHandshakeRequest } from "../../session/manager.js";
15
+ import { ProofGenerator } from "../../proof/generator.js";
16
+ import { ProofVerifier } from "../../proof/verifier.js";
17
+ import { ClockProvider, FetchProvider } from "../../providers/base.js";
18
+ import {
19
+ createDidKeyResolver,
20
+ resolveDidKeySync,
21
+ extractPublicKeyFromDidKey,
22
+ publicKeyToJwk,
23
+ } from "../../delegation/did-key-resolver.js";
24
+ import type { DIDDocument } from "../../delegation/vc-verifier.js";
25
+ import type { DetachedProof, StatusList2021Credential, DelegationRecord } from "../../types/protocol.js";
26
+
27
+ // Minimal concrete providers for the ProofVerifier
28
+ class TestClockProvider extends ClockProvider {
29
+ now(): number {
30
+ return Date.now();
31
+ }
32
+ isWithinSkew(timestampMs: number, skewSeconds: number): boolean {
33
+ const diff = Math.abs(Date.now() - timestampMs);
34
+ return diff <= skewSeconds * 1000;
35
+ }
36
+ hasExpired(expiresAt: number): boolean {
37
+ return Date.now() > expiresAt;
38
+ }
39
+ calculateExpiry(ttlSeconds: number): number {
40
+ return Date.now() + ttlSeconds * 1000;
41
+ }
42
+ format(timestamp: number): string {
43
+ return new Date(timestamp).toISOString();
44
+ }
45
+ }
46
+
47
+ class TestFetchProvider extends FetchProvider {
48
+ private didResolver = createDidKeyResolver();
49
+
50
+ async resolveDID(did: string): Promise<DIDDocument | null> {
51
+ // createDidKeyResolver returns a DIDResolver { resolve(did) }
52
+ return this.didResolver.resolve(did);
53
+ }
54
+
55
+ async fetchStatusList(_url: string): Promise<StatusList2021Credential | null> {
56
+ return null;
57
+ }
58
+
59
+ async fetchDelegationChain(_id: string): Promise<DelegationRecord[]> {
60
+ return [];
61
+ }
62
+
63
+ async fetch(_url: string, _options?: unknown): Promise<Response> {
64
+ throw new Error("Not implemented");
65
+ }
66
+ }
67
+
68
+ describe("MCP-I Full Protocol Flow", () => {
69
+ it("handshake → session → tool call → proof → verification", async () => {
70
+ const cryptoProvider = new NodeCryptoProvider();
71
+
72
+ // ── Step 1: Generate agent identity ──────────────────────────
73
+ const identityProvider = new MemoryIdentityProvider(cryptoProvider);
74
+ const agent = await identityProvider.getIdentity();
75
+
76
+ expect(agent.did).toMatch(/^did:key:z/);
77
+ expect(agent.kid).toMatch(/#keys-1$/);
78
+
79
+ // ── Step 2: Establish session via handshake ──────────────────
80
+ const serverDid = "did:web:test-server.example.com";
81
+ const sessionManager = new SessionManager(cryptoProvider, {
82
+ sessionTtlMinutes: 30,
83
+ timestampSkewSeconds: 120,
84
+ serverDid,
85
+ });
86
+
87
+ const handshakeRequest = createHandshakeRequest(serverDid);
88
+ handshakeRequest.agentDid = agent.did;
89
+
90
+ const handshakeResult = await sessionManager.validateHandshake(handshakeRequest);
91
+
92
+ expect(handshakeResult.success).toBe(true);
93
+ expect(handshakeResult.session).toBeDefined();
94
+ expect(handshakeResult.session!.sessionId).toMatch(/^mcpi_/);
95
+ expect(handshakeResult.session!.agentDid).toBe(agent.did);
96
+ expect(handshakeResult.session!.audience).toBe(serverDid);
97
+
98
+ const session = handshakeResult.session!;
99
+
100
+ // Verify session is retrievable
101
+ const retrievedSession = await sessionManager.getSession(session.sessionId);
102
+ expect(retrievedSession).not.toBeNull();
103
+ expect(retrievedSession!.sessionId).toBe(session.sessionId);
104
+
105
+ // ── Step 3: Simulate a tool call ─────────────────────────────
106
+ const toolRequest = {
107
+ method: "tools/call",
108
+ params: {
109
+ name: "read_file",
110
+ arguments: { path: "/etc/hosts" },
111
+ },
112
+ };
113
+
114
+ const toolResponse = {
115
+ data: {
116
+ content: [
117
+ {
118
+ type: "text",
119
+ text: "127.0.0.1 localhost\n::1 localhost",
120
+ },
121
+ ],
122
+ },
123
+ };
124
+
125
+ // ── Step 4: Generate proof for the tool call ─────────────────
126
+ const proofGenerator = new ProofGenerator(agent, cryptoProvider);
127
+ const proof = await proofGenerator.generateProof(
128
+ toolRequest,
129
+ toolResponse,
130
+ session,
131
+ );
132
+
133
+ // Verify proof structure
134
+ expect(proof.jws).toBeDefined();
135
+ expect(proof.jws.split(".")).toHaveLength(3); // JWS compact format
136
+ expect(proof.meta.did).toBe(agent.did);
137
+ expect(proof.meta.kid).toBe(agent.kid);
138
+ expect(proof.meta.audience).toBe(serverDid);
139
+ expect(proof.meta.sessionId).toBe(session.sessionId);
140
+ expect(proof.meta.nonce).toBe(session.nonce);
141
+ expect(proof.meta.requestHash).toMatch(/^sha256:[a-f0-9]{64}$/);
142
+ expect(proof.meta.responseHash).toMatch(/^sha256:[a-f0-9]{64}$/);
143
+ expect(proof.meta.ts).toBeGreaterThan(0);
144
+
145
+ // ── Step 5: Verify proof with ProofVerifier ──────────────────
146
+ const verifier = new ProofVerifier({
147
+ cryptoProvider,
148
+ clockProvider: new TestClockProvider() ,
149
+ nonceCacheProvider: new MemoryNonceCacheProvider(),
150
+ fetchProvider: new TestFetchProvider() ,
151
+ timestampSkewSeconds: 300,
152
+ });
153
+
154
+ // Resolve the agent's public key via DID:key
155
+ const publicKeyJwk = await verifier.fetchPublicKeyFromDID(agent.did);
156
+ expect(publicKeyJwk).not.toBeNull();
157
+ expect(publicKeyJwk!.kty).toBe("OKP");
158
+ expect(publicKeyJwk!.crv).toBe("Ed25519");
159
+
160
+ // Align kid from agent identity
161
+ publicKeyJwk!.kid = agent.kid;
162
+
163
+ // Verify the proof
164
+ const verificationResult = await verifier.verifyProof(proof, publicKeyJwk!);
165
+
166
+ expect(verificationResult.valid).toBe(true);
167
+ expect(verificationResult.reason).toBeUndefined();
168
+
169
+ // ── Step 6: Verify replay protection ─────────────────────────
170
+ // Same proof should be rejected (nonce already used)
171
+ const replayResult = await verifier.verifyProof(proof, publicKeyJwk!);
172
+
173
+ expect(replayResult.valid).toBe(false);
174
+ expect(replayResult.reason).toContain("Nonce already used");
175
+ });
176
+
177
+ it("should reject proof with tampered response hash", async () => {
178
+ const cryptoProvider = new NodeCryptoProvider();
179
+ const identityProvider = new MemoryIdentityProvider(cryptoProvider);
180
+ const agent = await identityProvider.getIdentity();
181
+
182
+ const serverDid = "did:web:test-server.example.com";
183
+ const sessionManager = new SessionManager(cryptoProvider, {
184
+ sessionTtlMinutes: 30,
185
+ serverDid,
186
+ });
187
+
188
+ const handshakeRequest = createHandshakeRequest(serverDid);
189
+ handshakeRequest.agentDid = agent.did;
190
+
191
+ const { session } = await sessionManager.validateHandshake(handshakeRequest);
192
+
193
+ const proofGenerator = new ProofGenerator(agent, cryptoProvider);
194
+ const proof = await proofGenerator.generateProof(
195
+ { method: "tools/call", params: { name: "echo" } },
196
+ { data: { text: "hello" } },
197
+ session!,
198
+ );
199
+
200
+ // Tamper with the response hash
201
+ const tamperedProof: DetachedProof = {
202
+ jws: proof.jws,
203
+ meta: {
204
+ ...proof.meta,
205
+ responseHash: "sha256:0000000000000000000000000000000000000000000000000000000000000000",
206
+ },
207
+ };
208
+
209
+ const verifier = new ProofVerifier({
210
+ cryptoProvider,
211
+ clockProvider: new TestClockProvider() ,
212
+ nonceCacheProvider: new MemoryNonceCacheProvider(),
213
+ fetchProvider: new TestFetchProvider() ,
214
+ timestampSkewSeconds: 300,
215
+ });
216
+
217
+ const publicKeyJwk = await verifier.fetchPublicKeyFromDID(agent.did);
218
+ // Align kid (see main flow test comment)
219
+ publicKeyJwk!.kid = agent.kid;
220
+ const result = await verifier.verifyProof(tamperedProof, publicKeyJwk!);
221
+
222
+ // The JWS signature was computed over the original meta — the tampered meta
223
+ // produces a different canonical payload, so signature verification fails.
224
+ expect(result.valid).toBe(false);
225
+ });
226
+
227
+ it("should generate unique proofs for different tool calls in same session", async () => {
228
+ const cryptoProvider = new NodeCryptoProvider();
229
+ const identityProvider = new MemoryIdentityProvider(cryptoProvider);
230
+ const agent = await identityProvider.getIdentity();
231
+
232
+ const serverDid = "did:web:test-server.example.com";
233
+ const sessionManager = new SessionManager(cryptoProvider, {
234
+ sessionTtlMinutes: 30,
235
+ serverDid,
236
+ });
237
+
238
+ const handshakeRequest = createHandshakeRequest(serverDid);
239
+ handshakeRequest.agentDid = agent.did;
240
+ const { session } = await sessionManager.validateHandshake(handshakeRequest);
241
+
242
+ const proofGenerator = new ProofGenerator(agent, cryptoProvider);
243
+
244
+ const proof1 = await proofGenerator.generateProof(
245
+ { method: "tools/call", params: { name: "tool-a" } },
246
+ { data: { result: "a" } },
247
+ session!,
248
+ );
249
+
250
+ const proof2 = await proofGenerator.generateProof(
251
+ { method: "tools/call", params: { name: "tool-b" } },
252
+ { data: { result: "b" } },
253
+ session!,
254
+ );
255
+
256
+ // Different tool calls produce different hashes
257
+ expect(proof1.meta.requestHash).not.toBe(proof2.meta.requestHash);
258
+ expect(proof1.meta.responseHash).not.toBe(proof2.meta.responseHash);
259
+
260
+ // Different JWS signatures
261
+ expect(proof1.jws).not.toBe(proof2.jws);
262
+
263
+ // But same identity and session context
264
+ expect(proof1.meta.did).toBe(proof2.meta.did);
265
+ expect(proof1.meta.sessionId).toBe(proof2.meta.sessionId);
266
+ });
267
+
268
+ it("should verify proof using ProofGenerator.verifyProof (self-verification)", async () => {
269
+ const cryptoProvider = new NodeCryptoProvider();
270
+ const identityProvider = new MemoryIdentityProvider(cryptoProvider);
271
+ const agent = await identityProvider.getIdentity();
272
+
273
+ const serverDid = "did:web:test-server.example.com";
274
+ const sessionManager = new SessionManager(cryptoProvider, {
275
+ sessionTtlMinutes: 30,
276
+ serverDid,
277
+ });
278
+
279
+ const handshakeRequest = createHandshakeRequest(serverDid);
280
+ handshakeRequest.agentDid = agent.did;
281
+ const { session } = await sessionManager.validateHandshake(handshakeRequest);
282
+
283
+ const request = { method: "tools/call", params: { name: "echo", arguments: { msg: "hi" } } };
284
+ const response = { data: { echo: "hi" } };
285
+
286
+ const proofGenerator = new ProofGenerator(agent, cryptoProvider);
287
+ const proof = await proofGenerator.generateProof(request, response, session!);
288
+
289
+ // ProofGenerator can self-verify (uses same agent's public key)
290
+ const selfVerified = await proofGenerator.verifyProof(proof, request, response);
291
+ expect(selfVerified).toBe(true);
292
+
293
+ // Tampered response should fail self-verification
294
+ const tamperedResponse = { data: { echo: "tampered" } };
295
+ const tamperedVerified = await proofGenerator.verifyProof(proof, request, tamperedResponse);
296
+ expect(tamperedVerified).toBe(false);
297
+ });
298
+
299
+ it("handshake replay should be rejected", async () => {
300
+ const cryptoProvider = new NodeCryptoProvider();
301
+
302
+ const serverDid = "did:web:test-server.example.com";
303
+ const sessionManager = new SessionManager(cryptoProvider, {
304
+ sessionTtlMinutes: 30,
305
+ serverDid,
306
+ });
307
+
308
+ const handshakeRequest = createHandshakeRequest(serverDid);
309
+ handshakeRequest.agentDid = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
310
+
311
+ // First handshake succeeds
312
+ const result1 = await sessionManager.validateHandshake(handshakeRequest);
313
+ expect(result1.success).toBe(true);
314
+
315
+ // Replayed handshake (same nonce) should fail
316
+ const result2 = await sessionManager.validateHandshake(handshakeRequest);
317
+ expect(result2.success).toBe(false);
318
+ expect(result2.error?.message).toContain("Nonce already used");
319
+ });
320
+
321
+ it("DID:key resolution round-trip", async () => {
322
+ const cryptoProvider = new NodeCryptoProvider();
323
+ const identityProvider = new MemoryIdentityProvider(cryptoProvider);
324
+ const agent = await identityProvider.getIdentity();
325
+
326
+ // Resolve the DID we just generated using sync resolver
327
+ const resolved = resolveDidKeySync(agent.did);
328
+
329
+ expect(resolved).not.toBeNull();
330
+ expect(resolved!.id).toBe(agent.did);
331
+ expect(resolved!.verificationMethod).toBeDefined();
332
+ expect(resolved!.verificationMethod!.length).toBeGreaterThan(0);
333
+
334
+ const vm = resolved!.verificationMethod![0]!;
335
+ expect(vm.publicKeyJwk).toBeDefined();
336
+
337
+ const jwk = vm.publicKeyJwk as { kty: string; crv: string; x: string };
338
+ expect(jwk.kty).toBe("OKP");
339
+ expect(jwk.crv).toBe("Ed25519");
340
+ expect(jwk.x).toBeDefined();
341
+
342
+ // Also test extractPublicKeyFromDidKey + publicKeyToJwk round-trip
343
+ const extractedBytes = extractPublicKeyFromDidKey(agent.did);
344
+ expect(extractedBytes).not.toBeNull();
345
+ const reconstructedJwk = publicKeyToJwk(extractedBytes!);
346
+ expect(reconstructedJwk.x).toBe(jwk.x);
347
+
348
+ // Sign something with the agent's private key
349
+ const message = new TextEncoder().encode("test message");
350
+ const signature = await cryptoProvider.sign(message, agent.privateKey);
351
+
352
+ // Decode the public key from the JWK x parameter and verify
353
+ const xBase64url = jwk.x;
354
+ // Convert base64url to base64
355
+ const xBase64 = xBase64url
356
+ .replace(/-/g, "+")
357
+ .replace(/_/g, "/")
358
+ + "=".repeat((4 - (xBase64url.length % 4)) % 4);
359
+ const verified = await cryptoProvider.verify(message, signature, xBase64);
360
+ expect(verified).toBe(true);
361
+ });
362
+ });
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Tests for Base Provider Classes
3
+ *
4
+ * These tests verify that the abstract base classes are properly defined
5
+ * and that implementations must provide all required methods.
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+ import {
10
+ CryptoProvider,
11
+ ClockProvider,
12
+ FetchProvider,
13
+ StorageProvider,
14
+ NonceCacheProvider,
15
+ IdentityProvider
16
+ } from '../../providers/base.js';
17
+
18
+ describe('Base Provider Classes', () => {
19
+ describe('CryptoProvider', () => {
20
+ it('should be defined as a class', () => {
21
+ expect(CryptoProvider).toBeDefined();
22
+ expect(typeof CryptoProvider).toBe('function');
23
+ });
24
+
25
+ it('should require implementation of abstract methods', () => {
26
+ // TypeScript abstract methods don't exist at runtime
27
+ // They're enforced at compile-time, not runtime
28
+ class TestCrypto extends CryptoProvider {}
29
+ const instance = new TestCrypto();
30
+
31
+ // These methods are undefined because they're abstract
32
+ expect(instance.sign).toBeUndefined();
33
+ expect(instance.verify).toBeUndefined();
34
+ expect(instance.generateKeyPair).toBeUndefined();
35
+ expect(instance.hash).toBeUndefined();
36
+ expect(instance.randomBytes).toBeUndefined();
37
+ });
38
+
39
+ it('should work when properly implemented', async () => {
40
+ class TestCrypto extends CryptoProvider {
41
+ async sign(data: Uint8Array, privateKey: string): Promise<Uint8Array> {
42
+ return new Uint8Array([1, 2, 3]);
43
+ }
44
+ async verify(data: Uint8Array, signature: Uint8Array, publicKey: string): Promise<boolean> {
45
+ return true;
46
+ }
47
+ async generateKeyPair(): Promise<{ privateKey: string; publicKey: string }> {
48
+ return { privateKey: 'test-private', publicKey: 'test-public' };
49
+ }
50
+ async hash(data: Uint8Array): Promise<Uint8Array> {
51
+ return new Uint8Array([4, 5, 6]);
52
+ }
53
+ async randomBytes(length: number): Promise<Uint8Array> {
54
+ return new Uint8Array(length);
55
+ }
56
+ }
57
+
58
+ const instance = new TestCrypto();
59
+ expect(await instance.sign(new Uint8Array(), '')).toEqual(new Uint8Array([1, 2, 3]));
60
+ expect(await instance.verify(new Uint8Array(), new Uint8Array(), '')).toBe(true);
61
+ expect(await instance.generateKeyPair()).toEqual({ privateKey: 'test-private', publicKey: 'test-public' });
62
+ expect(await instance.hash(new Uint8Array())).toEqual(new Uint8Array([4, 5, 6]));
63
+ expect(await instance.randomBytes(5)).toHaveLength(5);
64
+ });
65
+ });
66
+
67
+ describe('ClockProvider', () => {
68
+ it('should be defined as a class', () => {
69
+ expect(ClockProvider).toBeDefined();
70
+ expect(typeof ClockProvider).toBe('function');
71
+ });
72
+
73
+ it('should require implementation of abstract methods', () => {
74
+ class TestClock extends ClockProvider {}
75
+ const instance = new TestClock();
76
+
77
+ expect(instance.now).toBeUndefined();
78
+ expect(instance.isWithinSkew).toBeUndefined();
79
+ expect(instance.hasExpired).toBeUndefined();
80
+ expect(instance.calculateExpiry).toBeUndefined();
81
+ expect(instance.format).toBeUndefined();
82
+ });
83
+ });
84
+
85
+ describe('FetchProvider', () => {
86
+ it('should be defined as a class', () => {
87
+ expect(FetchProvider).toBeDefined();
88
+ expect(typeof FetchProvider).toBe('function');
89
+ });
90
+
91
+ it('should require implementation of abstract methods', () => {
92
+ class TestFetch extends FetchProvider {}
93
+ const instance = new TestFetch();
94
+
95
+ expect(instance.resolveDID).toBeUndefined();
96
+ expect(instance.fetchStatusList).toBeUndefined();
97
+ expect(instance.fetchDelegationChain).toBeUndefined();
98
+ expect(instance.fetch).toBeUndefined();
99
+ });
100
+ });
101
+
102
+ describe('StorageProvider', () => {
103
+ it('should be defined as a class', () => {
104
+ expect(StorageProvider).toBeDefined();
105
+ expect(typeof StorageProvider).toBe('function');
106
+ });
107
+
108
+ it('should require implementation of abstract methods', () => {
109
+ class TestStorage extends StorageProvider {}
110
+ const instance = new TestStorage();
111
+
112
+ expect(instance.get).toBeUndefined();
113
+ expect(instance.set).toBeUndefined();
114
+ expect(instance.delete).toBeUndefined();
115
+ expect(instance.exists).toBeUndefined();
116
+ expect(instance.list).toBeUndefined();
117
+ });
118
+ });
119
+
120
+ describe('NonceCacheProvider', () => {
121
+ it('should be defined as a class', () => {
122
+ expect(NonceCacheProvider).toBeDefined();
123
+ expect(typeof NonceCacheProvider).toBe('function');
124
+ });
125
+
126
+ it('should require implementation of abstract methods', () => {
127
+ class TestNonceCache extends NonceCacheProvider {}
128
+ const instance = new TestNonceCache();
129
+
130
+ expect(instance.has).toBeUndefined();
131
+ expect(instance.add).toBeUndefined();
132
+ expect(instance.cleanup).toBeUndefined();
133
+ expect(instance.destroy).toBeUndefined();
134
+ });
135
+ });
136
+
137
+ describe('IdentityProvider', () => {
138
+ it('should be defined as a class', () => {
139
+ expect(IdentityProvider).toBeDefined();
140
+ expect(typeof IdentityProvider).toBe('function');
141
+ });
142
+
143
+ it('should require implementation of abstract methods', () => {
144
+ class TestIdentity extends IdentityProvider {}
145
+ const instance = new TestIdentity();
146
+
147
+ expect(instance.getIdentity).toBeUndefined();
148
+ expect(instance.saveIdentity).toBeUndefined();
149
+ expect(instance.rotateKeys).toBeUndefined();
150
+ expect(instance.deleteIdentity).toBeUndefined();
151
+ });
152
+ });
153
+
154
+ describe('AgentIdentity interface', () => {
155
+ it('should have proper type structure', () => {
156
+ const validIdentity = {
157
+ did: 'did:key:z123',
158
+ kid: 'did:key:z123#keys-1',
159
+ privateKey: 'private-key',
160
+ publicKey: 'public-key',
161
+ createdAt: new Date().toISOString(),
162
+ type: 'development' as const,
163
+ metadata: { foo: 'bar' }
164
+ };
165
+
166
+ // Type checking is done at compile time
167
+ // This test just verifies the shape is correct
168
+ expect(validIdentity.did).toBe('did:key:z123');
169
+ expect(validIdentity.type).toBe('development');
170
+ expect(validIdentity.metadata).toEqual({ foo: 'bar' });
171
+ });
172
+ });
173
+ });