@mcp-i/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +390 -0
  3. package/dist/auth/handshake.d.ts +104 -0
  4. package/dist/auth/handshake.d.ts.map +1 -0
  5. package/dist/auth/handshake.js +230 -0
  6. package/dist/auth/handshake.js.map +1 -0
  7. package/dist/auth/index.d.ts +3 -0
  8. package/dist/auth/index.d.ts.map +1 -0
  9. package/dist/auth/index.js +2 -0
  10. package/dist/auth/index.js.map +1 -0
  11. package/dist/auth/types.d.ts +31 -0
  12. package/dist/auth/types.d.ts.map +1 -0
  13. package/dist/auth/types.js +7 -0
  14. package/dist/auth/types.js.map +1 -0
  15. package/dist/delegation/audience-validator.d.ts +9 -0
  16. package/dist/delegation/audience-validator.d.ts.map +1 -0
  17. package/dist/delegation/audience-validator.js +17 -0
  18. package/dist/delegation/audience-validator.js.map +1 -0
  19. package/dist/delegation/bitstring.d.ts +37 -0
  20. package/dist/delegation/bitstring.d.ts.map +1 -0
  21. package/dist/delegation/bitstring.js +117 -0
  22. package/dist/delegation/bitstring.js.map +1 -0
  23. package/dist/delegation/cascading-revocation.d.ts +45 -0
  24. package/dist/delegation/cascading-revocation.d.ts.map +1 -0
  25. package/dist/delegation/cascading-revocation.js +148 -0
  26. package/dist/delegation/cascading-revocation.js.map +1 -0
  27. package/dist/delegation/delegation-graph.d.ts +49 -0
  28. package/dist/delegation/delegation-graph.d.ts.map +1 -0
  29. package/dist/delegation/delegation-graph.js +99 -0
  30. package/dist/delegation/delegation-graph.js.map +1 -0
  31. package/dist/delegation/did-key-resolver.d.ts +64 -0
  32. package/dist/delegation/did-key-resolver.d.ts.map +1 -0
  33. package/dist/delegation/did-key-resolver.js +154 -0
  34. package/dist/delegation/did-key-resolver.js.map +1 -0
  35. package/dist/delegation/did-web-resolver.d.ts +83 -0
  36. package/dist/delegation/did-web-resolver.d.ts.map +1 -0
  37. package/dist/delegation/did-web-resolver.js +218 -0
  38. package/dist/delegation/did-web-resolver.js.map +1 -0
  39. package/dist/delegation/index.d.ts +21 -0
  40. package/dist/delegation/index.d.ts.map +1 -0
  41. package/dist/delegation/index.js +21 -0
  42. package/dist/delegation/index.js.map +1 -0
  43. package/dist/delegation/outbound-headers.d.ts +81 -0
  44. package/dist/delegation/outbound-headers.d.ts.map +1 -0
  45. package/dist/delegation/outbound-headers.js +139 -0
  46. package/dist/delegation/outbound-headers.js.map +1 -0
  47. package/dist/delegation/outbound-proof.d.ts +43 -0
  48. package/dist/delegation/outbound-proof.d.ts.map +1 -0
  49. package/dist/delegation/outbound-proof.js +52 -0
  50. package/dist/delegation/outbound-proof.js.map +1 -0
  51. package/dist/delegation/statuslist-manager.d.ts +44 -0
  52. package/dist/delegation/statuslist-manager.d.ts.map +1 -0
  53. package/dist/delegation/statuslist-manager.js +126 -0
  54. package/dist/delegation/statuslist-manager.js.map +1 -0
  55. package/dist/delegation/storage/memory-graph-storage.d.ts +70 -0
  56. package/dist/delegation/storage/memory-graph-storage.d.ts.map +1 -0
  57. package/dist/delegation/storage/memory-graph-storage.js +145 -0
  58. package/dist/delegation/storage/memory-graph-storage.js.map +1 -0
  59. package/dist/delegation/storage/memory-statuslist-storage.d.ts +19 -0
  60. package/dist/delegation/storage/memory-statuslist-storage.d.ts.map +1 -0
  61. package/dist/delegation/storage/memory-statuslist-storage.js +33 -0
  62. package/dist/delegation/storage/memory-statuslist-storage.js.map +1 -0
  63. package/dist/delegation/utils.d.ts +49 -0
  64. package/dist/delegation/utils.d.ts.map +1 -0
  65. package/dist/delegation/utils.js +131 -0
  66. package/dist/delegation/utils.js.map +1 -0
  67. package/dist/delegation/vc-issuer.d.ts +56 -0
  68. package/dist/delegation/vc-issuer.d.ts.map +1 -0
  69. package/dist/delegation/vc-issuer.js +80 -0
  70. package/dist/delegation/vc-issuer.js.map +1 -0
  71. package/dist/delegation/vc-verifier.d.ts +112 -0
  72. package/dist/delegation/vc-verifier.d.ts.map +1 -0
  73. package/dist/delegation/vc-verifier.js +280 -0
  74. package/dist/delegation/vc-verifier.js.map +1 -0
  75. package/dist/index.d.ts +45 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +53 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/logging/index.d.ts +2 -0
  80. package/dist/logging/index.d.ts.map +1 -0
  81. package/dist/logging/index.js +2 -0
  82. package/dist/logging/index.js.map +1 -0
  83. package/dist/logging/logger.d.ts +23 -0
  84. package/dist/logging/logger.d.ts.map +1 -0
  85. package/dist/logging/logger.js +82 -0
  86. package/dist/logging/logger.js.map +1 -0
  87. package/dist/middleware/index.d.ts +7 -0
  88. package/dist/middleware/index.d.ts.map +1 -0
  89. package/dist/middleware/index.js +7 -0
  90. package/dist/middleware/index.js.map +1 -0
  91. package/dist/middleware/with-mcpi.d.ts +152 -0
  92. package/dist/middleware/with-mcpi.d.ts.map +1 -0
  93. package/dist/middleware/with-mcpi.js +472 -0
  94. package/dist/middleware/with-mcpi.js.map +1 -0
  95. package/dist/proof/errors.d.ts +49 -0
  96. package/dist/proof/errors.d.ts.map +1 -0
  97. package/dist/proof/errors.js +61 -0
  98. package/dist/proof/errors.js.map +1 -0
  99. package/dist/proof/generator.d.ts +65 -0
  100. package/dist/proof/generator.d.ts.map +1 -0
  101. package/dist/proof/generator.js +163 -0
  102. package/dist/proof/generator.js.map +1 -0
  103. package/dist/proof/index.d.ts +4 -0
  104. package/dist/proof/index.d.ts.map +1 -0
  105. package/dist/proof/index.js +4 -0
  106. package/dist/proof/index.js.map +1 -0
  107. package/dist/proof/verifier.d.ts +108 -0
  108. package/dist/proof/verifier.d.ts.map +1 -0
  109. package/dist/proof/verifier.js +299 -0
  110. package/dist/proof/verifier.js.map +1 -0
  111. package/dist/providers/base.d.ts +64 -0
  112. package/dist/providers/base.d.ts.map +1 -0
  113. package/dist/providers/base.js +19 -0
  114. package/dist/providers/base.js.map +1 -0
  115. package/dist/providers/index.d.ts +3 -0
  116. package/dist/providers/index.d.ts.map +1 -0
  117. package/dist/providers/index.js +3 -0
  118. package/dist/providers/index.js.map +1 -0
  119. package/dist/providers/memory.d.ts +33 -0
  120. package/dist/providers/memory.d.ts.map +1 -0
  121. package/dist/providers/memory.js +102 -0
  122. package/dist/providers/memory.js.map +1 -0
  123. package/dist/session/index.d.ts +2 -0
  124. package/dist/session/index.d.ts.map +1 -0
  125. package/dist/session/index.js +2 -0
  126. package/dist/session/index.js.map +1 -0
  127. package/dist/session/manager.d.ts +77 -0
  128. package/dist/session/manager.d.ts.map +1 -0
  129. package/dist/session/manager.js +251 -0
  130. package/dist/session/manager.js.map +1 -0
  131. package/dist/types/protocol.d.ts +320 -0
  132. package/dist/types/protocol.d.ts.map +1 -0
  133. package/dist/types/protocol.js +229 -0
  134. package/dist/types/protocol.js.map +1 -0
  135. package/dist/utils/base58.d.ts +31 -0
  136. package/dist/utils/base58.d.ts.map +1 -0
  137. package/dist/utils/base58.js +104 -0
  138. package/dist/utils/base58.js.map +1 -0
  139. package/dist/utils/base64.d.ts +13 -0
  140. package/dist/utils/base64.d.ts.map +1 -0
  141. package/dist/utils/base64.js +99 -0
  142. package/dist/utils/base64.js.map +1 -0
  143. package/dist/utils/crypto-service.d.ts +37 -0
  144. package/dist/utils/crypto-service.d.ts.map +1 -0
  145. package/dist/utils/crypto-service.js +153 -0
  146. package/dist/utils/crypto-service.js.map +1 -0
  147. package/dist/utils/did-helpers.d.ts +156 -0
  148. package/dist/utils/did-helpers.d.ts.map +1 -0
  149. package/dist/utils/did-helpers.js +193 -0
  150. package/dist/utils/did-helpers.js.map +1 -0
  151. package/dist/utils/ed25519-constants.d.ts +18 -0
  152. package/dist/utils/ed25519-constants.d.ts.map +1 -0
  153. package/dist/utils/ed25519-constants.js +21 -0
  154. package/dist/utils/ed25519-constants.js.map +1 -0
  155. package/dist/utils/index.d.ts +5 -0
  156. package/dist/utils/index.d.ts.map +1 -0
  157. package/dist/utils/index.js +5 -0
  158. package/dist/utils/index.js.map +1 -0
  159. package/package.json +105 -0
  160. package/src/__tests__/integration/full-flow.test.ts +362 -0
  161. package/src/__tests__/providers/base.test.ts +173 -0
  162. package/src/__tests__/providers/memory.test.ts +332 -0
  163. package/src/__tests__/utils/mock-providers.ts +319 -0
  164. package/src/__tests__/utils/node-crypto-provider.ts +93 -0
  165. package/src/auth/handshake.ts +411 -0
  166. package/src/auth/index.ts +11 -0
  167. package/src/auth/types.ts +40 -0
  168. package/src/delegation/__tests__/audience-validator.test.ts +110 -0
  169. package/src/delegation/__tests__/bitstring.test.ts +346 -0
  170. package/src/delegation/__tests__/cascading-revocation.test.ts +624 -0
  171. package/src/delegation/__tests__/delegation-graph.test.ts +623 -0
  172. package/src/delegation/__tests__/did-key-resolver.test.ts +265 -0
  173. package/src/delegation/__tests__/did-web-resolver.test.ts +467 -0
  174. package/src/delegation/__tests__/outbound-headers.test.ts +230 -0
  175. package/src/delegation/__tests__/outbound-proof.test.ts +179 -0
  176. package/src/delegation/__tests__/statuslist-manager.test.ts +515 -0
  177. package/src/delegation/__tests__/utils.test.ts +185 -0
  178. package/src/delegation/__tests__/vc-issuer.test.ts +487 -0
  179. package/src/delegation/__tests__/vc-verifier.test.ts +1029 -0
  180. package/src/delegation/audience-validator.ts +24 -0
  181. package/src/delegation/bitstring.ts +160 -0
  182. package/src/delegation/cascading-revocation.ts +224 -0
  183. package/src/delegation/delegation-graph.ts +143 -0
  184. package/src/delegation/did-key-resolver.ts +181 -0
  185. package/src/delegation/did-web-resolver.ts +270 -0
  186. package/src/delegation/index.ts +33 -0
  187. package/src/delegation/outbound-headers.ts +193 -0
  188. package/src/delegation/outbound-proof.ts +90 -0
  189. package/src/delegation/statuslist-manager.ts +219 -0
  190. package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +366 -0
  191. package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +228 -0
  192. package/src/delegation/storage/memory-graph-storage.ts +178 -0
  193. package/src/delegation/storage/memory-statuslist-storage.ts +42 -0
  194. package/src/delegation/utils.ts +189 -0
  195. package/src/delegation/vc-issuer.ts +137 -0
  196. package/src/delegation/vc-verifier.ts +440 -0
  197. package/src/index.ts +264 -0
  198. package/src/logging/__tests__/logger.test.ts +366 -0
  199. package/src/logging/index.ts +6 -0
  200. package/src/logging/logger.ts +91 -0
  201. package/src/middleware/__tests__/with-mcpi.test.ts +504 -0
  202. package/src/middleware/index.ts +16 -0
  203. package/src/middleware/with-mcpi.ts +766 -0
  204. package/src/proof/__tests__/proof-generator.test.ts +483 -0
  205. package/src/proof/__tests__/verifier.test.ts +488 -0
  206. package/src/proof/errors.ts +75 -0
  207. package/src/proof/generator.ts +255 -0
  208. package/src/proof/index.ts +22 -0
  209. package/src/proof/verifier.ts +449 -0
  210. package/src/providers/base.ts +68 -0
  211. package/src/providers/index.ts +15 -0
  212. package/src/providers/memory.ts +130 -0
  213. package/src/session/__tests__/session-manager.test.ts +342 -0
  214. package/src/session/index.ts +7 -0
  215. package/src/session/manager.ts +332 -0
  216. package/src/types/protocol.ts +596 -0
  217. package/src/utils/__tests__/base58.test.ts +281 -0
  218. package/src/utils/__tests__/base64.test.ts +239 -0
  219. package/src/utils/__tests__/crypto-service.test.ts +530 -0
  220. package/src/utils/__tests__/did-helpers.test.ts +156 -0
  221. package/src/utils/base58.ts +115 -0
  222. package/src/utils/base64.ts +116 -0
  223. package/src/utils/crypto-service.ts +209 -0
  224. package/src/utils/did-helpers.ts +210 -0
  225. package/src/utils/ed25519-constants.ts +23 -0
  226. package/src/utils/index.ts +9 -0
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Delegation Credential Issuer (Platform-Agnostic)
3
+ *
4
+ * Issues W3C Verifiable Credentials for delegations with Ed25519 signatures.
5
+ * Follows the Python POC design (Delegation-Service.md:136-163) where
6
+ * delegations are issued AS W3C VCs.
7
+ *
8
+ * Related Spec: MCP-I §4.1, §4.2, W3C VC Data Model 1.1
9
+ */
10
+
11
+ import type {
12
+ DelegationCredential,
13
+ DelegationRecord,
14
+ CredentialStatus,
15
+ Proof,
16
+ } from '../types/protocol.js';
17
+ import { wrapDelegationAsVC } from '../types/protocol.js';
18
+ import { canonicalizeJSON } from './utils.js';
19
+
20
+ export interface IssueDelegationOptions {
21
+ id?: string;
22
+ issuanceDate?: string;
23
+ expirationDate?: string;
24
+ credentialStatus?: CredentialStatus;
25
+ additionalContexts?: string[];
26
+ }
27
+
28
+ export interface VCSigningFunction {
29
+ (canonicalVC: string, issuerDid: string, kid: string): Promise<Proof>;
30
+ }
31
+
32
+ export interface IdentityProvider {
33
+ getDid(): string;
34
+ getKeyId(): string;
35
+ getPrivateKey(): string;
36
+ }
37
+
38
+ export class DelegationCredentialIssuer {
39
+ constructor(
40
+ private identity: IdentityProvider,
41
+ private signingFunction: VCSigningFunction
42
+ ) {}
43
+
44
+ async issueDelegationCredential(
45
+ delegation: DelegationRecord,
46
+ options: IssueDelegationOptions = {}
47
+ ): Promise<DelegationCredential> {
48
+ let unsignedVC = wrapDelegationAsVC(delegation, {
49
+ id: options.id,
50
+ issuanceDate: options.issuanceDate,
51
+ expirationDate: options.expirationDate,
52
+ credentialStatus: options.credentialStatus,
53
+ });
54
+
55
+ if (options.additionalContexts && options.additionalContexts.length > 0) {
56
+ const existingContexts = unsignedVC['@context'] as Array<string | Record<string, unknown>>;
57
+ unsignedVC = {
58
+ ...unsignedVC,
59
+ '@context': [...existingContexts, ...options.additionalContexts],
60
+ };
61
+ }
62
+
63
+ const canonicalVC = this.canonicalizeVC(unsignedVC);
64
+
65
+ const proof = await this.signingFunction(
66
+ canonicalVC,
67
+ this.identity.getDid(),
68
+ this.identity.getKeyId()
69
+ );
70
+
71
+ return {
72
+ ...unsignedVC,
73
+ proof,
74
+ } as DelegationCredential;
75
+ }
76
+
77
+ /**
78
+ * Create and issue a delegation credential in one step.
79
+ *
80
+ * Constructs a DelegationRecord from the provided parameters and wraps it
81
+ * in a signed W3C Verifiable Credential.
82
+ *
83
+ * @param params - Delegation parameters including issuer/subject DIDs, constraints, etc.
84
+ * @param options - Optional VC issuance options (id, dates, status, contexts)
85
+ * @returns Signed W3C Delegation Credential
86
+ */
87
+ async createAndIssueDelegation(
88
+ params: {
89
+ id: string;
90
+ issuerDid: string;
91
+ subjectDid: string;
92
+ controller?: string;
93
+ parentId?: string;
94
+ constraints: DelegationRecord['constraints'];
95
+ status?: DelegationRecord['status'];
96
+ metadata?: Record<string, unknown>;
97
+ },
98
+ options: IssueDelegationOptions = {}
99
+ ): Promise<DelegationCredential> {
100
+ const now = Date.now();
101
+
102
+ const delegation: DelegationRecord = {
103
+ id: params.id,
104
+ issuerDid: params.issuerDid,
105
+ subjectDid: params.subjectDid,
106
+ controller: params.controller,
107
+ vcId: options.id || `urn:uuid:${params.id}`,
108
+ parentId: params.parentId,
109
+ constraints: params.constraints,
110
+ signature: '',
111
+ status: params.status || 'active',
112
+ createdAt: now,
113
+ metadata: params.metadata,
114
+ };
115
+
116
+ return this.issueDelegationCredential(delegation, options);
117
+ }
118
+
119
+ private canonicalizeVC(vc: Omit<DelegationCredential, 'proof'>): string {
120
+ return canonicalizeJSON(vc);
121
+ }
122
+
123
+ getIssuerDid(): string {
124
+ return this.identity.getDid();
125
+ }
126
+
127
+ getIssuerKeyId(): string {
128
+ return this.identity.getKeyId();
129
+ }
130
+ }
131
+
132
+ export function createDelegationIssuer(
133
+ identity: IdentityProvider,
134
+ signingFunction: VCSigningFunction
135
+ ): DelegationCredentialIssuer {
136
+ return new DelegationCredentialIssuer(identity, signingFunction);
137
+ }
@@ -0,0 +1,440 @@
1
+ /**
2
+ * Dylan Hobbs
3
+ * Delegation Credential Verifier (Platform-Agnostic)
4
+ *
5
+ * Progressive enhancement verification for W3C Delegation Credentials.
6
+ *
7
+ * Stage 1: Fast basic checks (no network, early rejection)
8
+ * Stage 2: Parallel advanced checks (signature, status)
9
+ * Stage 3: Combined results
10
+ *
11
+ * Related Spec: MCP-I §4.3, W3C VC Data Model 1.1
12
+ */
13
+
14
+ import type {
15
+ DelegationCredential,
16
+ CredentialStatus,
17
+ } from "../types/protocol.js";
18
+ import {
19
+ isDelegationCredentialExpired,
20
+ isDelegationCredentialNotYetValid,
21
+ validateDelegationCredential,
22
+ } from "../types/protocol.js";
23
+
24
+ export interface DelegationVCVerificationResult {
25
+ valid: boolean;
26
+ reason?: string;
27
+ stage: "basic" | "signature" | "status" | "complete";
28
+ cached?: boolean;
29
+ metrics?: {
30
+ basicCheckMs?: number;
31
+ signatureCheckMs?: number;
32
+ statusCheckMs?: number;
33
+ totalMs: number;
34
+ };
35
+ checks?: {
36
+ basicValid?: boolean;
37
+ signatureValid?: boolean;
38
+ statusValid?: boolean;
39
+ };
40
+ }
41
+
42
+ export interface VerifyDelegationVCOptions {
43
+ skipCache?: boolean;
44
+ skipSignature?: boolean;
45
+ skipStatus?: boolean;
46
+ didResolver?: DIDResolver;
47
+ statusListResolver?: StatusListResolver;
48
+ }
49
+
50
+ export interface DIDResolver {
51
+ resolve(did: string): Promise<DIDDocument | null>;
52
+ }
53
+
54
+ export interface DIDDocument {
55
+ id: string;
56
+ verificationMethod?: VerificationMethod[];
57
+ authentication?: (string | VerificationMethod)[];
58
+ assertionMethod?: (string | VerificationMethod)[];
59
+ }
60
+
61
+ export interface VerificationMethod {
62
+ id: string;
63
+ type: string;
64
+ controller: string;
65
+ publicKeyJwk?: unknown;
66
+ publicKeyBase58?: string;
67
+ publicKeyMultibase?: string;
68
+ }
69
+
70
+ export interface StatusListResolver {
71
+ checkStatus(status: CredentialStatus): Promise<boolean>;
72
+ }
73
+
74
+ export interface SignatureVerificationFunction {
75
+ (
76
+ vc: DelegationCredential,
77
+ publicKeyJwk: unknown,
78
+ ): Promise<{
79
+ valid: boolean;
80
+ reason?: string;
81
+ }>;
82
+ }
83
+
84
+ export class DelegationCredentialVerifier {
85
+ private didResolver?: DIDResolver;
86
+ private statusListResolver?: StatusListResolver;
87
+ private signatureVerifier?: SignatureVerificationFunction;
88
+ private cache = new Map<
89
+ string,
90
+ { result: DelegationVCVerificationResult; expiresAt: number }
91
+ >();
92
+ private cacheInsertionOrder: string[] = [];
93
+ private cacheTtl: number;
94
+ /**
95
+ * Maximum number of entries in the verification cache.
96
+ * In production deployments, configure maxCacheSize based on expected concurrent delegations.
97
+ * Default of 1000 is suitable for most use cases.
98
+ */
99
+ private maxCacheSize: number;
100
+
101
+ constructor(options?: {
102
+ didResolver?: DIDResolver;
103
+ statusListResolver?: StatusListResolver;
104
+ signatureVerifier?: SignatureVerificationFunction;
105
+ cacheTtl?: number;
106
+ /** Maximum cache entries. Default: 1000 */
107
+ maxCacheSize?: number;
108
+ }) {
109
+ this.didResolver = options?.didResolver;
110
+ this.statusListResolver = options?.statusListResolver;
111
+ this.signatureVerifier = options?.signatureVerifier;
112
+ this.cacheTtl = options?.cacheTtl || 60_000;
113
+ this.maxCacheSize = options?.maxCacheSize ?? 1000;
114
+ }
115
+
116
+ /**
117
+ * Verify a delegation credential through progressive enhancement.
118
+ *
119
+ * Stage 1: Fast basic checks (schema, expiry, status field)
120
+ * Stage 2: Parallel signature and status list checks (if resolvers configured)
121
+ * Stage 3: Combined result with timing metrics
122
+ *
123
+ * @param vc - The W3C Delegation Credential to verify
124
+ * @param options - Verification options (skip cache/signature/status, custom resolvers)
125
+ * @returns Verification result with validity, reason, stage reached, and metrics
126
+ */
127
+ async verifyDelegationCredential(
128
+ vc: DelegationCredential,
129
+ options: VerifyDelegationVCOptions = {},
130
+ ): Promise<DelegationVCVerificationResult> {
131
+ const startTime = Date.now();
132
+
133
+ if (!options.skipCache) {
134
+ const cached = this.getFromCache(vc.id || "");
135
+ if (cached) {
136
+ return { ...cached, cached: true };
137
+ }
138
+ }
139
+
140
+ const basicCheckStart = Date.now();
141
+ const basicValidation = this.validateBasicProperties(vc);
142
+ const basicCheckMs = Date.now() - basicCheckStart;
143
+
144
+ if (!basicValidation.valid) {
145
+ const result: DelegationVCVerificationResult = {
146
+ valid: false,
147
+ reason: basicValidation.reason,
148
+ stage: "basic",
149
+ metrics: {
150
+ basicCheckMs,
151
+ totalMs: Date.now() - startTime,
152
+ },
153
+ checks: {
154
+ basicValid: false,
155
+ },
156
+ };
157
+ return result;
158
+ }
159
+
160
+ const signaturePromise = !options.skipSignature
161
+ ? this.verifySignature(vc, options.didResolver || this.didResolver)
162
+ : Promise.resolve<{
163
+ valid: boolean;
164
+ reason?: string;
165
+ durationMs?: number;
166
+ }>({
167
+ valid: true,
168
+ durationMs: 0,
169
+ });
170
+
171
+ const statusPromise =
172
+ !options.skipStatus && vc.credentialStatus
173
+ ? this.checkCredentialStatus(
174
+ vc.credentialStatus,
175
+ options.statusListResolver || this.statusListResolver,
176
+ )
177
+ : Promise.resolve<{
178
+ valid: boolean;
179
+ reason?: string;
180
+ durationMs?: number;
181
+ }>({
182
+ valid: true,
183
+ durationMs: 0,
184
+ });
185
+
186
+ const [signatureResult, statusResult] = await Promise.all([
187
+ signaturePromise,
188
+ statusPromise,
189
+ ]);
190
+
191
+ const signatureCheckMs = signatureResult.durationMs || 0;
192
+ const statusCheckMs = statusResult.durationMs || 0;
193
+
194
+ const allValid =
195
+ basicValidation.valid && signatureResult.valid && statusResult.valid;
196
+
197
+ const result: DelegationVCVerificationResult = {
198
+ valid: allValid,
199
+ reason: !allValid
200
+ ? signatureResult.reason || statusResult.reason || "Unknown failure"
201
+ : undefined,
202
+ stage: "complete",
203
+ metrics: {
204
+ basicCheckMs,
205
+ signatureCheckMs,
206
+ statusCheckMs,
207
+ totalMs: Date.now() - startTime,
208
+ },
209
+ checks: {
210
+ basicValid: basicValidation.valid,
211
+ signatureValid: signatureResult.valid,
212
+ statusValid: statusResult.valid,
213
+ },
214
+ };
215
+
216
+ if (result.valid && vc.id) {
217
+ this.setInCache(vc.id, result);
218
+ }
219
+
220
+ return result;
221
+ }
222
+
223
+ private validateBasicProperties(vc: DelegationCredential): {
224
+ valid: boolean;
225
+ reason?: string;
226
+ } {
227
+ const schemaValidation = validateDelegationCredential(vc);
228
+ if (!schemaValidation.success) {
229
+ return {
230
+ valid: false,
231
+ reason: `Schema validation failed: ${schemaValidation.error?.message}`,
232
+ };
233
+ }
234
+
235
+ if (isDelegationCredentialExpired(vc)) {
236
+ return { valid: false, reason: "Delegation credential expired" };
237
+ }
238
+
239
+ if (isDelegationCredentialNotYetValid(vc)) {
240
+ return { valid: false, reason: "Delegation credential not yet valid" };
241
+ }
242
+
243
+ const delegation = vc.credentialSubject.delegation;
244
+ if (delegation.status === "revoked") {
245
+ return { valid: false, reason: "Delegation status is revoked" };
246
+ }
247
+ if (delegation.status === "expired") {
248
+ return { valid: false, reason: "Delegation status is expired" };
249
+ }
250
+
251
+ if (!delegation.issuerDid || !delegation.subjectDid) {
252
+ return { valid: false, reason: "Missing issuer or subject DID" };
253
+ }
254
+
255
+ if (!vc.proof) {
256
+ return { valid: false, reason: "Missing proof" };
257
+ }
258
+
259
+ return { valid: true };
260
+ }
261
+
262
+ private async verifySignature(
263
+ vc: DelegationCredential,
264
+ didResolver?: DIDResolver,
265
+ ): Promise<{ valid: boolean; reason?: string; durationMs?: number }> {
266
+ const startTime = Date.now();
267
+
268
+ try {
269
+ const issuerDid =
270
+ typeof vc.issuer === "string" ? vc.issuer : vc.issuer.id;
271
+
272
+ if (!didResolver || !this.signatureVerifier) {
273
+ return {
274
+ valid: false,
275
+ reason:
276
+ "No DID resolver or signature verifier configured — signature cannot be verified",
277
+ durationMs: Date.now() - startTime,
278
+ };
279
+ }
280
+
281
+ const didDoc = await didResolver.resolve(issuerDid);
282
+ if (!didDoc) {
283
+ return {
284
+ valid: false,
285
+ reason: `Could not resolve issuer DID: ${issuerDid}`,
286
+ durationMs: Date.now() - startTime,
287
+ };
288
+ }
289
+
290
+ if (!vc.proof) {
291
+ return {
292
+ valid: false,
293
+ reason: "Proof is missing",
294
+ durationMs: Date.now() - startTime,
295
+ };
296
+ }
297
+
298
+ const verificationMethodId = vc.proof["verificationMethod"];
299
+ if (!verificationMethodId) {
300
+ return {
301
+ valid: false,
302
+ reason: "Proof missing verificationMethod",
303
+ durationMs: Date.now() - startTime,
304
+ };
305
+ }
306
+
307
+ const verificationMethod = this.findVerificationMethod(
308
+ didDoc,
309
+ verificationMethodId as string,
310
+ );
311
+ if (!verificationMethod) {
312
+ return {
313
+ valid: false,
314
+ reason: `Verification method ${verificationMethodId} not found`,
315
+ durationMs: Date.now() - startTime,
316
+ };
317
+ }
318
+
319
+ const publicKeyJwk = verificationMethod.publicKeyJwk;
320
+ if (!publicKeyJwk) {
321
+ return {
322
+ valid: false,
323
+ reason: "Verification method missing publicKeyJwk",
324
+ durationMs: Date.now() - startTime,
325
+ };
326
+ }
327
+
328
+ const verificationResult = await this.signatureVerifier(vc, publicKeyJwk);
329
+
330
+ return {
331
+ valid: verificationResult.valid,
332
+ reason: verificationResult.reason,
333
+ durationMs: Date.now() - startTime,
334
+ };
335
+ } catch (error) {
336
+ return {
337
+ valid: false,
338
+ reason: `Signature verification error: ${error instanceof Error ? error.message : "Unknown error"}`,
339
+ durationMs: Date.now() - startTime,
340
+ };
341
+ }
342
+ }
343
+
344
+ private async checkCredentialStatus(
345
+ status: CredentialStatus,
346
+ statusListResolver?: StatusListResolver,
347
+ ): Promise<{ valid: boolean; reason?: string; durationMs?: number }> {
348
+ const startTime = Date.now();
349
+
350
+ try {
351
+ if (!statusListResolver) {
352
+ return {
353
+ valid: true,
354
+ reason: "No status list resolver available, skipping status check",
355
+ durationMs: Date.now() - startTime,
356
+ };
357
+ }
358
+
359
+ const isRevoked = await statusListResolver.checkStatus(status);
360
+
361
+ if (isRevoked) {
362
+ return {
363
+ valid: false,
364
+ reason: `Credential revoked via StatusList2021 (${status.statusPurpose})`,
365
+ durationMs: Date.now() - startTime,
366
+ };
367
+ }
368
+
369
+ return {
370
+ valid: true,
371
+ durationMs: Date.now() - startTime,
372
+ };
373
+ } catch (error) {
374
+ return {
375
+ valid: false,
376
+ reason: `Status check error: ${error instanceof Error ? error.message : "Unknown error"}`,
377
+ durationMs: Date.now() - startTime,
378
+ };
379
+ }
380
+ }
381
+
382
+ private findVerificationMethod(
383
+ didDoc: DIDDocument,
384
+ verificationMethodId: string,
385
+ ): VerificationMethod | undefined {
386
+ return didDoc.verificationMethod?.find(
387
+ (vm) => vm.id === verificationMethodId,
388
+ );
389
+ }
390
+
391
+ private getFromCache(id: string): DelegationVCVerificationResult | null {
392
+ const entry = this.cache.get(id);
393
+ if (!entry) return null;
394
+
395
+ if (Date.now() > entry.expiresAt) {
396
+ this.cache.delete(id);
397
+ return null;
398
+ }
399
+
400
+ return entry.result;
401
+ }
402
+
403
+ private setInCache(id: string, result: DelegationVCVerificationResult): void {
404
+ // Evict oldest entry if cache exceeds maxCacheSize (simple FIFO)
405
+ while (this.cache.size >= this.maxCacheSize && this.cacheInsertionOrder.length > 0) {
406
+ const oldestId = this.cacheInsertionOrder.shift();
407
+ if (oldestId) {
408
+ this.cache.delete(oldestId);
409
+ }
410
+ }
411
+
412
+ this.cache.set(id, {
413
+ result,
414
+ expiresAt: Date.now() + this.cacheTtl,
415
+ });
416
+ this.cacheInsertionOrder.push(id);
417
+ }
418
+
419
+ clearCache(): void {
420
+ this.cache.clear();
421
+ this.cacheInsertionOrder = [];
422
+ }
423
+
424
+ clearCacheEntry(id: string): void {
425
+ this.cache.delete(id);
426
+ const idx = this.cacheInsertionOrder.indexOf(id);
427
+ if (idx !== -1) {
428
+ this.cacheInsertionOrder.splice(idx, 1);
429
+ }
430
+ }
431
+ }
432
+
433
+ export function createDelegationVerifier(options?: {
434
+ didResolver?: DIDResolver;
435
+ statusListResolver?: StatusListResolver;
436
+ signatureVerifier?: SignatureVerificationFunction;
437
+ cacheTtl?: number;
438
+ }): DelegationCredentialVerifier {
439
+ return new DelegationCredentialVerifier(options);
440
+ }