@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
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
|
+
});
|