attestix 0.4.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/dist/index.js ADDED
@@ -0,0 +1,893 @@
1
+ // src/errors.ts
2
+ var AttestixError = class extends Error {
3
+ statusCode;
4
+ detail;
5
+ constructor(message, statusCode, detail) {
6
+ super(message);
7
+ this.name = "AttestixError";
8
+ this.statusCode = statusCode;
9
+ this.detail = detail ?? message;
10
+ const ErrorWithCapture = Error;
11
+ if (ErrorWithCapture.captureStackTrace) {
12
+ ErrorWithCapture.captureStackTrace(this, this.constructor);
13
+ }
14
+ }
15
+ };
16
+ var AttestixAuthError = class extends AttestixError {
17
+ constructor(detail) {
18
+ super(
19
+ detail ?? "Authentication failed - check your API key",
20
+ 401,
21
+ detail
22
+ );
23
+ this.name = "AttestixAuthError";
24
+ }
25
+ };
26
+ var AttestixNotFoundError = class extends AttestixError {
27
+ constructor(detail) {
28
+ super(
29
+ detail ?? "Resource not found",
30
+ 404,
31
+ detail
32
+ );
33
+ this.name = "AttestixNotFoundError";
34
+ }
35
+ };
36
+ var AttestixValidationError = class extends AttestixError {
37
+ constructor(detail) {
38
+ super(
39
+ detail ?? "Validation error - check your request parameters",
40
+ 400,
41
+ detail
42
+ );
43
+ this.name = "AttestixValidationError";
44
+ }
45
+ };
46
+ var AttestixRateLimitError = class extends AttestixError {
47
+ retryAfter;
48
+ constructor(detail, retryAfter) {
49
+ super(
50
+ detail ?? "Rate limit exceeded - try again later",
51
+ 429,
52
+ detail
53
+ );
54
+ this.name = "AttestixRateLimitError";
55
+ this.retryAfter = retryAfter ?? null;
56
+ }
57
+ };
58
+
59
+ // src/client.ts
60
+ var DEFAULT_TIMEOUT = 3e4;
61
+ var RETRY_STATUS_CODES = /* @__PURE__ */ new Set([429, 503]);
62
+ var MAX_RETRIES = 1;
63
+ var BASE_RETRY_DELAY = 1e3;
64
+ var AttestixClient = class {
65
+ baseUrl;
66
+ apiKey;
67
+ timeout;
68
+ constructor(options) {
69
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
70
+ this.apiKey = options.apiKey;
71
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
72
+ }
73
+ // ========================================================================
74
+ // Identity
75
+ // ========================================================================
76
+ async createIdentity(params) {
77
+ return this.request("POST", "/identities", params);
78
+ }
79
+ async getIdentity(agentId) {
80
+ return this.request("GET", `/identities/${encodeURIComponent(agentId)}`);
81
+ }
82
+ async listIdentities(params) {
83
+ const query = this.buildQuery(params);
84
+ return this.request("GET", `/identities${query}`);
85
+ }
86
+ async verifyIdentity(agentId) {
87
+ return this.request("POST", `/identities/${encodeURIComponent(agentId)}/verify`);
88
+ }
89
+ async translateIdentity(agentId, format) {
90
+ const query = this.buildQuery({ format });
91
+ return this.request("GET", `/identities/${encodeURIComponent(agentId)}/translate${query}`);
92
+ }
93
+ async revokeIdentity(agentId, reason) {
94
+ return this.request("POST", `/identities/${encodeURIComponent(agentId)}/revoke`, { reason });
95
+ }
96
+ async purgeAgentData(agentId) {
97
+ return this.request("DELETE", `/identities/${encodeURIComponent(agentId)}/purge`);
98
+ }
99
+ // ========================================================================
100
+ // Credentials
101
+ // ========================================================================
102
+ async issueCredential(params) {
103
+ return this.request("POST", "/credentials", params);
104
+ }
105
+ async getCredential(credentialId) {
106
+ return this.request("GET", `/credentials/${encodeURIComponent(credentialId)}`);
107
+ }
108
+ async listCredentials(params) {
109
+ const query = this.buildQuery(params);
110
+ return this.request("GET", `/credentials${query}`);
111
+ }
112
+ async verifyCredential(credentialId) {
113
+ return this.request("POST", `/credentials/${encodeURIComponent(credentialId)}/verify`);
114
+ }
115
+ async verifyExternalCredential(credential) {
116
+ return this.request("POST", "/credentials/verify-external", credential);
117
+ }
118
+ async revokeCredential(credentialId, reason) {
119
+ return this.request("POST", `/credentials/${encodeURIComponent(credentialId)}/revoke`, { reason });
120
+ }
121
+ async createPresentation(params) {
122
+ return this.request("POST", "/credentials/presentations", params);
123
+ }
124
+ // ========================================================================
125
+ // Compliance
126
+ // ========================================================================
127
+ async createComplianceProfile(params) {
128
+ return this.request("POST", "/compliance/profiles", params);
129
+ }
130
+ async getComplianceProfile(profileId) {
131
+ return this.request("GET", `/compliance/profiles/${encodeURIComponent(profileId)}`);
132
+ }
133
+ async listComplianceProfiles() {
134
+ return this.request("GET", "/compliance/profiles");
135
+ }
136
+ async getComplianceStatus(profileId) {
137
+ return this.request("GET", `/compliance/profiles/${encodeURIComponent(profileId)}/status`);
138
+ }
139
+ async recordAssessment(params) {
140
+ return this.request("POST", "/compliance/assessments", params);
141
+ }
142
+ async generateDeclaration(params) {
143
+ return this.request("POST", "/compliance/declarations", params);
144
+ }
145
+ // ========================================================================
146
+ // Reputation
147
+ // ========================================================================
148
+ async recordInteraction(params) {
149
+ await this.request("POST", "/reputation/interactions", params);
150
+ }
151
+ async getReputation(agentId) {
152
+ return this.request("GET", `/reputation/${encodeURIComponent(agentId)}`);
153
+ }
154
+ async queryReputation(params) {
155
+ const query = this.buildQuery(params);
156
+ return this.request("GET", `/reputation${query}`);
157
+ }
158
+ // ========================================================================
159
+ // Provenance
160
+ // ========================================================================
161
+ async recordTrainingData(params) {
162
+ return this.request("POST", "/provenance/training-data", params);
163
+ }
164
+ async recordModelLineage(params) {
165
+ return this.request("POST", "/provenance/model-lineage", params);
166
+ }
167
+ async logAction(params) {
168
+ return this.request("POST", "/provenance/actions", params);
169
+ }
170
+ async getProvenance(agentId) {
171
+ return this.request("GET", `/provenance/${encodeURIComponent(agentId)}`);
172
+ }
173
+ async getAuditTrail(params) {
174
+ const query = this.buildQuery(params);
175
+ return this.request("GET", `/provenance/audit${query}`);
176
+ }
177
+ // ========================================================================
178
+ // Delegation
179
+ // ========================================================================
180
+ async createDelegation(params) {
181
+ return this.request("POST", "/delegations", params);
182
+ }
183
+ async listDelegations(params) {
184
+ const query = this.buildQuery(params);
185
+ return this.request("GET", `/delegations${query}`);
186
+ }
187
+ async verifyDelegation(token) {
188
+ return this.request("POST", "/delegations/verify", { token });
189
+ }
190
+ async revokeDelegation(delegationId) {
191
+ await this.request("POST", `/delegations/${encodeURIComponent(delegationId)}/revoke`);
192
+ }
193
+ // ========================================================================
194
+ // DID
195
+ // ========================================================================
196
+ async createDidKey() {
197
+ return this.request("POST", "/did/key");
198
+ }
199
+ async createDidWeb(domain) {
200
+ return this.request("POST", "/did/web", { domain });
201
+ }
202
+ async resolveDid(did) {
203
+ return this.request("GET", `/did/${encodeURIComponent(did)}`);
204
+ }
205
+ // ========================================================================
206
+ // Blockchain Anchoring
207
+ // ========================================================================
208
+ async anchorIdentity(agentId) {
209
+ return this.request("POST", `/anchoring/identities/${encodeURIComponent(agentId)}`);
210
+ }
211
+ async anchorCredential(credentialId) {
212
+ return this.request("POST", `/anchoring/credentials/${encodeURIComponent(credentialId)}`);
213
+ }
214
+ async anchorAuditBatch(entryIds) {
215
+ return this.request("POST", "/anchoring/audit-batch", { entry_ids: entryIds });
216
+ }
217
+ async verifyAnchor(anchorId) {
218
+ return this.request("POST", `/anchoring/${encodeURIComponent(anchorId)}/verify`);
219
+ }
220
+ async getAnchorStatus(anchorId) {
221
+ return this.request("GET", `/anchoring/${encodeURIComponent(anchorId)}`);
222
+ }
223
+ async estimateAnchorCost() {
224
+ return this.request("GET", "/anchoring/estimate");
225
+ }
226
+ // ========================================================================
227
+ // Internal
228
+ // ========================================================================
229
+ async request(method, path, body) {
230
+ return this.requestWithRetry(method, path, body, 0);
231
+ }
232
+ async requestWithRetry(method, path, body, attempt) {
233
+ const controller = new AbortController();
234
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
235
+ const url = `${this.baseUrl}${path}`;
236
+ const headers = {
237
+ "Authorization": `Bearer ${this.apiKey}`,
238
+ "Accept": "application/json"
239
+ };
240
+ const init = {
241
+ method,
242
+ headers,
243
+ signal: controller.signal
244
+ };
245
+ if (body !== void 0 && method !== "GET") {
246
+ headers["Content-Type"] = "application/json";
247
+ init.body = JSON.stringify(body);
248
+ }
249
+ let response;
250
+ try {
251
+ response = await fetch(url, init);
252
+ } catch (error) {
253
+ clearTimeout(timeoutId);
254
+ if (error instanceof DOMException && error.name === "AbortError") {
255
+ throw new AttestixError(
256
+ `Request timed out after ${this.timeout}ms`,
257
+ 0,
258
+ "Request timeout"
259
+ );
260
+ }
261
+ throw new AttestixError(
262
+ `Network error: ${error instanceof Error ? error.message : String(error)}`,
263
+ 0,
264
+ "Network error"
265
+ );
266
+ } finally {
267
+ clearTimeout(timeoutId);
268
+ }
269
+ if (RETRY_STATUS_CODES.has(response.status) && attempt < MAX_RETRIES) {
270
+ const retryAfter = response.headers.get("Retry-After");
271
+ const delay = retryAfter ? parseInt(retryAfter, 10) * 1e3 : BASE_RETRY_DELAY * Math.pow(2, attempt);
272
+ await this.sleep(delay);
273
+ return this.requestWithRetry(method, path, body, attempt + 1);
274
+ }
275
+ if (!response.ok) {
276
+ await this.handleErrorResponse(response);
277
+ }
278
+ if (response.status === 204) {
279
+ return void 0;
280
+ }
281
+ const text = await response.text();
282
+ if (!text) {
283
+ return void 0;
284
+ }
285
+ return JSON.parse(text);
286
+ }
287
+ async handleErrorResponse(response) {
288
+ let detail;
289
+ try {
290
+ const body = await response.json();
291
+ detail = body.detail ?? body.message ?? JSON.stringify(body);
292
+ } catch {
293
+ detail = `HTTP ${response.status}: ${response.statusText}`;
294
+ }
295
+ switch (response.status) {
296
+ case 400:
297
+ throw new AttestixValidationError(detail);
298
+ case 401:
299
+ case 403:
300
+ throw new AttestixAuthError(detail);
301
+ case 404:
302
+ throw new AttestixNotFoundError(detail);
303
+ case 429: {
304
+ const retryAfter = response.headers.get("Retry-After");
305
+ throw new AttestixRateLimitError(
306
+ detail,
307
+ retryAfter ? parseInt(retryAfter, 10) : void 0
308
+ );
309
+ }
310
+ default:
311
+ throw new AttestixError(detail, response.status, detail);
312
+ }
313
+ }
314
+ buildQuery(params) {
315
+ if (!params) return "";
316
+ const searchParams = new URLSearchParams();
317
+ const entries = Object.entries(params);
318
+ for (const [key, value] of entries) {
319
+ if (value !== void 0 && value !== null) {
320
+ searchParams.set(key, String(value));
321
+ }
322
+ }
323
+ const queryString = searchParams.toString();
324
+ return queryString ? `?${queryString}` : "";
325
+ }
326
+ sleep(ms) {
327
+ return new Promise((resolve) => setTimeout(resolve, ms));
328
+ }
329
+ };
330
+
331
+ // src/verify/jcs.ts
332
+ var JcsUnsupportedValueError = class extends Error {
333
+ constructor(message) {
334
+ super(message);
335
+ this.name = "JcsUnsupportedValueError";
336
+ }
337
+ };
338
+ function canonicalizeToString(value) {
339
+ return serialize(value);
340
+ }
341
+ function canonicalize(value) {
342
+ return new TextEncoder().encode(serialize(value));
343
+ }
344
+ function serialize(value) {
345
+ if (value === null) {
346
+ return "null";
347
+ }
348
+ const t = typeof value;
349
+ if (t === "boolean") {
350
+ return value ? "true" : "false";
351
+ }
352
+ if (t === "string") {
353
+ return serializeString(value);
354
+ }
355
+ if (t === "number") {
356
+ return serializeNumber(value);
357
+ }
358
+ if (Array.isArray(value)) {
359
+ return "[" + value.map((v) => serialize(v)).join(",") + "]";
360
+ }
361
+ if (t === "object") {
362
+ const obj = value;
363
+ const keys = Object.keys(obj).sort(compareByCodePoint);
364
+ const parts = [];
365
+ for (const k of keys) {
366
+ parts.push(serializeString(k) + ":" + serialize(obj[k]));
367
+ }
368
+ return "{" + parts.join(",") + "}";
369
+ }
370
+ throw new JcsUnsupportedValueError(
371
+ `Cannot canonicalize value of type ${t}`
372
+ );
373
+ }
374
+ function compareByCodePoint(a, b) {
375
+ const ai = a[Symbol.iterator]();
376
+ const bi = b[Symbol.iterator]();
377
+ for (; ; ) {
378
+ const an = ai.next();
379
+ const bn = bi.next();
380
+ if (an.done && bn.done) return 0;
381
+ if (an.done) return -1;
382
+ if (bn.done) return 1;
383
+ const ac = an.value.codePointAt(0);
384
+ const bc = bn.value.codePointAt(0);
385
+ if (ac !== bc) return ac - bc;
386
+ }
387
+ }
388
+ function serializeNumber(n) {
389
+ if (!Number.isFinite(n)) {
390
+ throw new JcsUnsupportedValueError(
391
+ `Non-finite number (${n}) cannot be canonicalized`
392
+ );
393
+ }
394
+ if (n === 0 && 1 / n === -Infinity) {
395
+ throw new JcsUnsupportedValueError(
396
+ "Signed zero (-0) cannot be canonicalized (Python emits -0.0)"
397
+ );
398
+ }
399
+ if (Number.isInteger(n)) {
400
+ const s2 = String(n);
401
+ if (s2.includes("e") || s2.includes("E")) {
402
+ throw new JcsUnsupportedValueError(
403
+ `Integer ${n} would serialize in exponential notation; use a string field instead`
404
+ );
405
+ }
406
+ return s2;
407
+ }
408
+ const s = String(n);
409
+ if (s.includes("e") || s.includes("E")) {
410
+ throw new JcsUnsupportedValueError(
411
+ `Float ${n} requires exponential notation; not supported for cross-engine canonicalization`
412
+ );
413
+ }
414
+ return s;
415
+ }
416
+ var ESCAPE_MAP = {
417
+ 8: "\\b",
418
+ 9: "\\t",
419
+ 10: "\\n",
420
+ 12: "\\f",
421
+ 13: "\\r",
422
+ 34: '\\"',
423
+ 92: "\\\\"
424
+ };
425
+ function serializeString(raw) {
426
+ const s = raw.normalize("NFC");
427
+ let out = '"';
428
+ for (const ch of s) {
429
+ const code = ch.codePointAt(0);
430
+ const mapped = ESCAPE_MAP[code];
431
+ if (mapped !== void 0) {
432
+ out += mapped;
433
+ } else if (code < 32) {
434
+ out += "\\u" + code.toString(16).padStart(4, "0");
435
+ } else {
436
+ out += ch;
437
+ }
438
+ }
439
+ out += '"';
440
+ return out;
441
+ }
442
+
443
+ // src/verify/base64url.ts
444
+ var STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
445
+ var DECODE = (() => {
446
+ const table = new Int16Array(128).fill(-1);
447
+ for (let i = 0; i < STD_ALPHABET.length; i++) {
448
+ table[STD_ALPHABET.charCodeAt(i)] = i;
449
+ }
450
+ table["-".charCodeAt(0)] = 62;
451
+ table["_".charCodeAt(0)] = 63;
452
+ return table;
453
+ })();
454
+ function base64urlDecode(input) {
455
+ const s = input.replace(/=+$/g, "").trim();
456
+ const len = s.length;
457
+ const outLen = Math.floor(len * 6 / 8);
458
+ const out = new Uint8Array(outLen);
459
+ let buffer = 0;
460
+ let bits = 0;
461
+ let pos = 0;
462
+ for (let i = 0; i < len; i++) {
463
+ const c = s.charCodeAt(i);
464
+ const val = c < 128 ? DECODE[c] : -1;
465
+ if (val === -1) {
466
+ throw new Error(`Invalid base64url character at index ${i}`);
467
+ }
468
+ buffer = buffer << 6 | val;
469
+ bits += 6;
470
+ if (bits >= 8) {
471
+ bits -= 8;
472
+ out[pos++] = buffer >> bits & 255;
473
+ }
474
+ }
475
+ return out;
476
+ }
477
+
478
+ // src/verify/didkey.ts
479
+ var ED25519_MULTICODEC_PREFIX = new Uint8Array([237, 1]);
480
+ var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
481
+ var BASE58_MAP = (() => {
482
+ const map = new Int16Array(128).fill(-1);
483
+ for (let i = 0; i < BASE58_ALPHABET.length; i++) {
484
+ map[BASE58_ALPHABET.charCodeAt(i)] = i;
485
+ }
486
+ return map;
487
+ })();
488
+ function base58btcEncode(bytes) {
489
+ if (bytes.length === 0) return "";
490
+ let zeros = 0;
491
+ while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
492
+ const digits = [0];
493
+ for (let i = zeros; i < bytes.length; i++) {
494
+ let carry = bytes[i];
495
+ for (let j = 0; j < digits.length; j++) {
496
+ carry += digits[j] << 8;
497
+ digits[j] = carry % 58;
498
+ carry = carry / 58 | 0;
499
+ }
500
+ while (carry > 0) {
501
+ digits.push(carry % 58);
502
+ carry = carry / 58 | 0;
503
+ }
504
+ }
505
+ let out = "1".repeat(zeros);
506
+ for (let i = digits.length - 1; i >= 0; i--) {
507
+ out += BASE58_ALPHABET[digits[i]];
508
+ }
509
+ return out;
510
+ }
511
+ function base58btcDecode(str) {
512
+ if (str.length === 0) return new Uint8Array(0);
513
+ let zeros = 0;
514
+ while (zeros < str.length && str[zeros] === "1") zeros++;
515
+ const bytes = [0];
516
+ for (let i = zeros; i < str.length; i++) {
517
+ const c = str.charCodeAt(i);
518
+ const val = c < 128 ? BASE58_MAP[c] : -1;
519
+ if (val === -1) {
520
+ throw new Error(`Invalid base58 character '${str[i]}' at index ${i}`);
521
+ }
522
+ let carry = val;
523
+ for (let j = 0; j < bytes.length; j++) {
524
+ carry += bytes[j] * 58;
525
+ bytes[j] = carry & 255;
526
+ carry >>= 8;
527
+ }
528
+ while (carry > 0) {
529
+ bytes.push(carry & 255);
530
+ carry >>= 8;
531
+ }
532
+ }
533
+ const out = new Uint8Array(zeros + bytes.length);
534
+ for (let i = 0; i < bytes.length; i++) {
535
+ out[zeros + i] = bytes[bytes.length - 1 - i];
536
+ }
537
+ return out;
538
+ }
539
+ function publicKeyToDidKey(rawPublicKey) {
540
+ if (rawPublicKey.length !== 32) {
541
+ throw new Error(
542
+ `Ed25519 public key must be 32 bytes, got ${rawPublicKey.length}`
543
+ );
544
+ }
545
+ const multicodec = new Uint8Array(2 + 32);
546
+ multicodec.set(ED25519_MULTICODEC_PREFIX, 0);
547
+ multicodec.set(rawPublicKey, 2);
548
+ return `did:key:z${base58btcEncode(multicodec)}`;
549
+ }
550
+ function didKeyToPublicKey(did) {
551
+ if (!did.startsWith("did:key:z")) {
552
+ throw new Error(`Invalid did:key format: ${did}`);
553
+ }
554
+ const encoded = did.slice("did:key:z".length);
555
+ const decoded = base58btcDecode(encoded);
556
+ if (decoded.length < 2 || decoded[0] !== ED25519_MULTICODEC_PREFIX[0] || decoded[1] !== ED25519_MULTICODEC_PREFIX[1]) {
557
+ throw new Error("Not an Ed25519 did:key (wrong multicodec prefix)");
558
+ }
559
+ const raw = decoded.slice(2);
560
+ if (raw.length !== 32) {
561
+ throw new Error(
562
+ `Decoded Ed25519 public key must be 32 bytes, got ${raw.length}`
563
+ );
564
+ }
565
+ return raw;
566
+ }
567
+ function didFromVerificationMethod(vm) {
568
+ const idx = vm.indexOf("#");
569
+ return idx === -1 ? vm : vm.slice(0, idx);
570
+ }
571
+
572
+ // src/verify/ed25519.ts
573
+ import { ed25519 } from "@noble/curves/ed25519.js";
574
+ function verifyEd25519(signature, message, publicKey) {
575
+ try {
576
+ if (signature.length !== 64 || publicKey.length !== 32) {
577
+ return false;
578
+ }
579
+ return ed25519.verify(signature, message, publicKey);
580
+ } catch {
581
+ return false;
582
+ }
583
+ }
584
+
585
+ // src/verify/credential.ts
586
+ var VC_MUTABLE_FIELDS = ["proof", "credentialStatus"];
587
+ function isObject(v) {
588
+ return typeof v === "object" && v !== null && !Array.isArray(v);
589
+ }
590
+ function stripFields(obj, fields) {
591
+ const out = {};
592
+ for (const k of Object.keys(obj)) {
593
+ if (!fields.includes(k)) out[k] = obj[k];
594
+ }
595
+ return out;
596
+ }
597
+ function verifyCredential(credential, options = {}) {
598
+ const checks = {
599
+ structure_valid: false,
600
+ signature_valid: false,
601
+ not_expired: false,
602
+ not_revoked: true
603
+ };
604
+ if (!isObject(credential)) {
605
+ return { valid: false, reason: "Credential is not an object", checks };
606
+ }
607
+ const type = credential.type;
608
+ const types = Array.isArray(type) ? type : [];
609
+ if (!types.includes("VerifiableCredential")) {
610
+ return { valid: false, reason: "Not a VerifiableCredential", checks };
611
+ }
612
+ checks.structure_valid = true;
613
+ const status = credential.credentialStatus;
614
+ if (isObject(status) && status.revoked === true) {
615
+ checks.not_revoked = false;
616
+ }
617
+ const checkExpiry = options.checkExpiry !== false;
618
+ const expStr = credential.expirationDate;
619
+ if (checkExpiry && typeof expStr === "string") {
620
+ const exp = Date.parse(expStr);
621
+ const now = options.now ?? Date.now();
622
+ checks.not_expired = Number.isNaN(exp) ? true : now < exp;
623
+ } else {
624
+ checks.not_expired = true;
625
+ }
626
+ const proof = credential.proof;
627
+ let issuer;
628
+ if (isObject(proof) && typeof proof.proofValue === "string") {
629
+ const vm = typeof proof.verificationMethod === "string" ? proof.verificationMethod : "";
630
+ let issuerDid = vm ? didFromVerificationMethod(vm) : "";
631
+ if (!issuerDid && isObject(credential.issuer)) {
632
+ const iid = credential.issuer.id;
633
+ if (typeof iid === "string") issuerDid = iid;
634
+ }
635
+ issuer = issuerDid;
636
+ try {
637
+ const payload = stripFields(credential, VC_MUTABLE_FIELDS);
638
+ const message = canonicalize(payload);
639
+ const sig = base64urlDecode(proof.proofValue);
640
+ const pub = didKeyToPublicKey(issuerDid);
641
+ checks.signature_valid = verifyEd25519(sig, message, pub);
642
+ } catch {
643
+ checks.signature_valid = false;
644
+ }
645
+ }
646
+ const subjectId = isObject(credential.credentialSubject) && typeof credential.credentialSubject.id === "string" ? credential.credentialSubject.id : void 0;
647
+ const valid = checks.structure_valid && checks.signature_valid && checks.not_expired && checks.not_revoked;
648
+ const result = {
649
+ valid,
650
+ checks,
651
+ type: types
652
+ };
653
+ if (issuer !== void 0) result.issuer = issuer;
654
+ if (subjectId !== void 0) result.subject = subjectId;
655
+ if (!valid) {
656
+ result.reason = !checks.signature_valid ? "Invalid signature" : !checks.not_expired ? "Credential expired" : !checks.not_revoked ? "Credential revoked" : "Verification failed";
657
+ }
658
+ return result;
659
+ }
660
+ function verifyPresentation(presentation, options = {}) {
661
+ const empty = {
662
+ valid: false,
663
+ vpSignatureValid: false,
664
+ credentialCount: 0,
665
+ credentialResults: []
666
+ };
667
+ if (!isObject(presentation)) {
668
+ return { ...empty, reason: "Presentation is not an object" };
669
+ }
670
+ const types = Array.isArray(presentation.type) ? presentation.type : [];
671
+ if (!types.includes("VerifiablePresentation")) {
672
+ return { ...empty, reason: "Not a VerifiablePresentation" };
673
+ }
674
+ const holder = typeof presentation.holder === "string" ? presentation.holder : void 0;
675
+ let vpSignatureValid = false;
676
+ const proof = presentation.proof;
677
+ if (isObject(proof) && typeof proof.proofValue === "string") {
678
+ const vm = typeof proof.verificationMethod === "string" ? proof.verificationMethod : "";
679
+ const issuerDid = vm ? didFromVerificationMethod(vm) : holder ?? "";
680
+ try {
681
+ const payload = stripFields(presentation, ["proof"]);
682
+ const message = canonicalize(payload);
683
+ const sig = base64urlDecode(proof.proofValue);
684
+ const pub = didKeyToPublicKey(issuerDid);
685
+ vpSignatureValid = verifyEd25519(sig, message, pub);
686
+ } catch {
687
+ vpSignatureValid = false;
688
+ }
689
+ }
690
+ const creds = Array.isArray(presentation.verifiableCredential) ? presentation.verifiableCredential : [];
691
+ const credentialResults = creds.map((c) => verifyCredential(c, options));
692
+ const credsValid = credentialResults.every((r) => r.valid);
693
+ const valid = vpSignatureValid && credsValid;
694
+ const result = {
695
+ valid,
696
+ vpSignatureValid,
697
+ credentialCount: creds.length,
698
+ credentialResults
699
+ };
700
+ if (holder !== void 0) result.holder = holder;
701
+ if (!valid) {
702
+ result.reason = !vpSignatureValid ? "Invalid presentation signature" : "One or more embedded credentials failed verification";
703
+ }
704
+ return result;
705
+ }
706
+
707
+ // src/verify/delegation.ts
708
+ function decodeJwt(token) {
709
+ const parts = token.split(".");
710
+ if (parts.length !== 3) {
711
+ throw new Error("Malformed JWT (expected 3 dot-separated segments)");
712
+ }
713
+ const [h, p, s] = parts;
714
+ const payloadJson = new TextDecoder().decode(base64urlDecode(p));
715
+ const claims = JSON.parse(payloadJson);
716
+ if (!Array.isArray(claims.att)) claims.att = [];
717
+ if (!Array.isArray(claims.prf)) claims.prf = [];
718
+ const signingInput = new TextEncoder().encode(`${h}.${p}`);
719
+ const signature = base64urlDecode(s);
720
+ return { claims, signingInput, signature };
721
+ }
722
+ function isSubset(child, parent) {
723
+ const parentSet = new Set(parent);
724
+ return child.every((c) => parentSet.has(c));
725
+ }
726
+ function verifyDelegationChain(token, options = {}) {
727
+ const now = options.now ?? Math.floor(Date.now() / 1e3);
728
+ const tol = options.clockToleranceSeconds ?? 0;
729
+ const links = [];
730
+ if (Array.isArray(token)) {
731
+ if (token.length === 0) {
732
+ return {
733
+ valid: false,
734
+ reason: "Empty delegation chain",
735
+ links,
736
+ capabilities: []
737
+ };
738
+ }
739
+ const decoded = [];
740
+ for (const t of token) {
741
+ let d;
742
+ try {
743
+ d = decodeJwt(t);
744
+ } catch (e) {
745
+ return {
746
+ valid: false,
747
+ reason: `Malformed token in chain: ${e.message}`,
748
+ links,
749
+ capabilities: []
750
+ };
751
+ }
752
+ decoded.push(d);
753
+ }
754
+ for (const d of decoded) {
755
+ const link = verifySingle(d, now, tol);
756
+ links.push(link);
757
+ if (!link.signatureValid) {
758
+ return { valid: false, reason: "Invalid signature in chain", links, capabilities: [] };
759
+ }
760
+ if (link.expired) {
761
+ return { valid: false, reason: "A delegation in the chain has expired", links, capabilities: [] };
762
+ }
763
+ if (link.notYetValid) {
764
+ return { valid: false, reason: "A delegation in the chain is not yet valid (nbf)", links, capabilities: [] };
765
+ }
766
+ }
767
+ for (let i = 0; i < decoded.length - 1; i++) {
768
+ const child = decoded[i].claims;
769
+ const parent = decoded[i + 1].claims;
770
+ if (!isSubset(child.att, parent.att)) {
771
+ return {
772
+ valid: false,
773
+ reason: "Capability escalation: child att is not a subset of parent att",
774
+ links,
775
+ capabilities: []
776
+ };
777
+ }
778
+ }
779
+ return { valid: true, links, capabilities: decoded[0].claims.att };
780
+ }
781
+ const seen = /* @__PURE__ */ new Set();
782
+ const result = verifyRecursive(token, now, tol, seen, links, null);
783
+ if (!result.ok) {
784
+ return { valid: false, reason: result.reason, links, capabilities: [] };
785
+ }
786
+ return { valid: true, links, capabilities: links[0]?.capabilities ?? [] };
787
+ }
788
+ function verifyRecursive(token, now, tol, seen, links, childAtt) {
789
+ let decoded;
790
+ try {
791
+ decoded = decodeJwt(token);
792
+ } catch (e) {
793
+ return { ok: false, reason: `Malformed token: ${e.message}` };
794
+ }
795
+ const link = verifySingle(decoded, now, tol);
796
+ links.push(link);
797
+ if (link.jti) {
798
+ if (seen.has(link.jti)) {
799
+ return { ok: false, reason: "Cycle detected in delegation proof chain" };
800
+ }
801
+ seen.add(link.jti);
802
+ }
803
+ if (!link.signatureValid) {
804
+ return { ok: false, reason: "Invalid token signature" };
805
+ }
806
+ if (link.expired) {
807
+ return { ok: false, reason: "Token has expired" };
808
+ }
809
+ if (link.notYetValid) {
810
+ return { ok: false, reason: "Token is not yet valid (nbf)" };
811
+ }
812
+ const att = decoded.claims.att;
813
+ if (childAtt !== null && !isSubset(childAtt, att)) {
814
+ return {
815
+ ok: false,
816
+ reason: "Capability escalation: child att is not a subset of parent att"
817
+ };
818
+ }
819
+ for (const parentToken of decoded.claims.prf) {
820
+ if (typeof parentToken !== "string" || !parentToken) {
821
+ return { ok: false, reason: "Malformed parent token in proof chain" };
822
+ }
823
+ const parentResult = verifyRecursive(
824
+ parentToken,
825
+ now,
826
+ tol,
827
+ seen,
828
+ links,
829
+ att
830
+ );
831
+ if (!parentResult.ok) {
832
+ return {
833
+ ok: false,
834
+ reason: `Invalid parent in proof chain: ${parentResult.reason}`
835
+ };
836
+ }
837
+ }
838
+ return { ok: true, att };
839
+ }
840
+ function verifySingle(decoded, now, tol) {
841
+ const c = decoded.claims;
842
+ let signatureValid = false;
843
+ try {
844
+ const pub = didKeyToPublicKey(c.iss);
845
+ signatureValid = verifyEd25519(
846
+ decoded.signature,
847
+ decoded.signingInput,
848
+ pub
849
+ );
850
+ } catch {
851
+ signatureValid = false;
852
+ }
853
+ const expired = typeof c.exp === "number" ? c.exp + tol < now : false;
854
+ const notYetValid = typeof c.nbf === "number" ? c.nbf - tol > now : false;
855
+ const link = {
856
+ issuer: c.iss,
857
+ capabilities: c.att,
858
+ signatureValid,
859
+ expired,
860
+ notYetValid
861
+ };
862
+ if (c.jti !== void 0) link.jti = c.jti;
863
+ if (c.delegator !== void 0) link.delegator = c.delegator;
864
+ if (c.aud !== void 0) link.audience = c.aud;
865
+ return link;
866
+ }
867
+ function decodeDelegationUnsafe(token) {
868
+ return decodeJwt(token).claims;
869
+ }
870
+ export {
871
+ AttestixAuthError,
872
+ AttestixClient,
873
+ AttestixError,
874
+ AttestixNotFoundError,
875
+ AttestixRateLimitError,
876
+ AttestixValidationError,
877
+ ED25519_MULTICODEC_PREFIX,
878
+ JcsUnsupportedValueError,
879
+ base58btcDecode,
880
+ base58btcEncode,
881
+ base64urlDecode,
882
+ canonicalize,
883
+ canonicalizeToString,
884
+ decodeDelegationUnsafe,
885
+ didFromVerificationMethod,
886
+ didKeyToPublicKey,
887
+ publicKeyToDidKey,
888
+ verifyCredential,
889
+ verifyDelegationChain,
890
+ verifyEd25519,
891
+ verifyPresentation
892
+ };
893
+ //# sourceMappingURL=index.js.map