@vess-id/status-list 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/dist/index.js ADDED
@@ -0,0 +1,1508 @@
1
+ import * as pako from 'pako';
2
+ import { SignJWT, jwtVerify } from 'jose';
3
+ import * as cbor from 'cbor';
4
+ import { createSign, createVerify } from 'crypto';
5
+
6
+ // lib/types/errors.ts
7
+ var StatusListError = class extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "StatusListError";
11
+ if (Error.captureStackTrace) {
12
+ Error.captureStackTrace(this, this.constructor);
13
+ }
14
+ }
15
+ };
16
+ var IndexOutOfBoundsError = class extends StatusListError {
17
+ constructor(index, maxIndex) {
18
+ super(`Index ${index} is out of bounds. Valid range: 0-${maxIndex}`);
19
+ this.name = "IndexOutOfBoundsError";
20
+ }
21
+ };
22
+ var InvalidBitSizeError = class extends StatusListError {
23
+ constructor(bits) {
24
+ super(`Invalid bit size: ${bits}. Must be one of: 1, 2, 4, or 8`);
25
+ this.name = "InvalidBitSizeError";
26
+ }
27
+ };
28
+ var InvalidStatusValueError = class extends StatusListError {
29
+ constructor(value, maxValue, bitsPerStatus) {
30
+ super(`Invalid status value: ${value}. For ${bitsPerStatus}-bit status, valid range is 0-${maxValue}`);
31
+ this.name = "InvalidStatusValueError";
32
+ }
33
+ };
34
+ var InvalidTokenFormatError = class extends StatusListError {
35
+ constructor(message) {
36
+ super(`Invalid token format: ${message}`);
37
+ this.name = "InvalidTokenFormatError";
38
+ }
39
+ };
40
+ var StatusListExpiredError = class extends StatusListError {
41
+ constructor(exp, currentTime) {
42
+ super(`Status list expired at ${exp} (current time: ${currentTime})`);
43
+ this.name = "StatusListExpiredError";
44
+ }
45
+ };
46
+ var FetchError = class extends StatusListError {
47
+ constructor(message) {
48
+ super(message);
49
+ this.name = "FetchError";
50
+ }
51
+ };
52
+ var MissingStatusListUriError = class extends StatusListError {
53
+ constructor(message = "Status list URI is missing from token") {
54
+ super(message);
55
+ this.name = "MissingStatusListUriError";
56
+ }
57
+ };
58
+ var CompressionError = class extends StatusListError {
59
+ constructor(message, cause) {
60
+ const fullMessage = cause ? `${message}: ${cause.message}` : message;
61
+ super(fullMessage);
62
+ this.name = "CompressionError";
63
+ this.cause = cause;
64
+ }
65
+ };
66
+ var ValidationError = class extends StatusListError {
67
+ errors;
68
+ constructor(errors) {
69
+ super(`Validation failed: ${errors.join(", ")}`);
70
+ this.name = "ValidationError";
71
+ this.errors = errors;
72
+ }
73
+ };
74
+
75
+ // lib/core/bitpack.ts
76
+ function validateBitSize(bits) {
77
+ if (bits !== 1 && bits !== 2 && bits !== 4 && bits !== 8) {
78
+ throw new InvalidBitSizeError(bits);
79
+ }
80
+ }
81
+ function validateStatusValue(value, bitsPerStatus) {
82
+ const maxValue = (1 << bitsPerStatus) - 1;
83
+ if (value < 0 || value > maxValue) {
84
+ throw new InvalidStatusValueError(value, maxValue, bitsPerStatus);
85
+ }
86
+ }
87
+ function packBits(statuses, bitsPerStatus) {
88
+ validateBitSize(bitsPerStatus);
89
+ const totalBits = statuses.length * bitsPerStatus;
90
+ const byteLength = Math.ceil(totalBits / 8);
91
+ const bytes = new Uint8Array(byteLength);
92
+ for (let i = 0; i < statuses.length; i++) {
93
+ const value = statuses[i];
94
+ validateStatusValue(value, bitsPerStatus);
95
+ const bitOffset = i * bitsPerStatus;
96
+ const byteIndex = Math.floor(bitOffset / 8);
97
+ const bitPosition = bitOffset % 8;
98
+ bytes[byteIndex] |= value << bitPosition;
99
+ if (bitPosition + bitsPerStatus > 8) {
100
+ const bitsInNextByte = bitPosition + bitsPerStatus - 8;
101
+ bytes[byteIndex + 1] |= value >> bitsPerStatus - bitsInNextByte;
102
+ }
103
+ }
104
+ return bytes;
105
+ }
106
+ function unpackBits(bytes, bitsPerStatus) {
107
+ validateBitSize(bitsPerStatus);
108
+ const totalBits = bytes.length * 8;
109
+ const statusCount = Math.floor(totalBits / bitsPerStatus);
110
+ const statuses = new Array(statusCount);
111
+ const mask = (1 << bitsPerStatus) - 1;
112
+ for (let i = 0; i < statusCount; i++) {
113
+ statuses[i] = getBitValue(bytes, i, bitsPerStatus, mask);
114
+ }
115
+ return statuses;
116
+ }
117
+ function getBitValue(bytes, index, bitsPerStatus, mask) {
118
+ validateBitSize(bitsPerStatus);
119
+ const totalBits = bytes.length * 8;
120
+ const maxIndex = Math.floor(totalBits / bitsPerStatus) - 1;
121
+ if (index < 0 || index > maxIndex) {
122
+ throw new IndexOutOfBoundsError(index, maxIndex);
123
+ }
124
+ const jump = bitsPerStatus * index;
125
+ const byteIndex = Math.floor(jump / 8);
126
+ const bitOffset = jump % 8;
127
+ const calculatedMask = mask ?? (1 << bitsPerStatus) - 1;
128
+ let value = bytes[byteIndex] >> bitOffset & calculatedMask;
129
+ if (bitOffset + bitsPerStatus > 8) {
130
+ const bitsInNextByte = bitOffset + bitsPerStatus - 8;
131
+ const nextByteBits = bytes[byteIndex + 1] & (1 << bitsInNextByte) - 1;
132
+ value |= nextByteBits << bitsPerStatus - bitsInNextByte;
133
+ }
134
+ return value;
135
+ }
136
+ function setBitValue(bytes, index, value, bitsPerStatus) {
137
+ validateBitSize(bitsPerStatus);
138
+ validateStatusValue(value, bitsPerStatus);
139
+ const totalBits = bytes.length * 8;
140
+ const maxIndex = Math.floor(totalBits / bitsPerStatus) - 1;
141
+ if (index < 0 || index > maxIndex) {
142
+ throw new IndexOutOfBoundsError(index, maxIndex);
143
+ }
144
+ const jump = bitsPerStatus * index;
145
+ const byteIndex = Math.floor(jump / 8);
146
+ const bitOffset = jump % 8;
147
+ const mask = (1 << bitsPerStatus) - 1;
148
+ bytes[byteIndex] &= ~(mask << bitOffset);
149
+ bytes[byteIndex] |= (value & mask) << bitOffset;
150
+ if (bitOffset + bitsPerStatus > 8) {
151
+ const bitsInNextByte = bitOffset + bitsPerStatus - 8;
152
+ const nextByteMask = (1 << bitsInNextByte) - 1;
153
+ bytes[byteIndex + 1] &= ~nextByteMask;
154
+ bytes[byteIndex + 1] |= value >> bitsPerStatus - bitsInNextByte & nextByteMask;
155
+ }
156
+ }
157
+ function calculateCapacity(byteLength, bitsPerStatus) {
158
+ validateBitSize(bitsPerStatus);
159
+ return Math.floor(byteLength * 8 / bitsPerStatus);
160
+ }
161
+ function compress(data) {
162
+ try {
163
+ return pako.deflate(data, { level: 9 });
164
+ } catch (error) {
165
+ throw new CompressionError("Failed to compress data", error);
166
+ }
167
+ }
168
+ function decompress(compressed) {
169
+ try {
170
+ return pako.inflate(compressed);
171
+ } catch (error) {
172
+ throw new CompressionError("Failed to decompress data", error);
173
+ }
174
+ }
175
+ function compressToBase64URL(data) {
176
+ const compressed = compress(data);
177
+ return base64UrlEncode(compressed);
178
+ }
179
+ function decompressFromBase64URL(base64url) {
180
+ try {
181
+ const compressed = base64UrlDecode(base64url);
182
+ return decompress(compressed);
183
+ } catch (error) {
184
+ if (error instanceof CompressionError) {
185
+ throw error;
186
+ }
187
+ throw new CompressionError("Failed to decode or decompress base64url data", error);
188
+ }
189
+ }
190
+ function base64UrlEncode(bytes) {
191
+ let base64 = "";
192
+ if (typeof Buffer !== "undefined") {
193
+ base64 = Buffer.from(bytes).toString("base64");
194
+ } else {
195
+ const binary = String.fromCharCode(...bytes);
196
+ base64 = btoa(binary);
197
+ }
198
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
199
+ }
200
+ function base64UrlDecode(base64url) {
201
+ let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
202
+ const padding = base64.length % 4;
203
+ if (padding > 0) {
204
+ base64 += "=".repeat(4 - padding);
205
+ }
206
+ if (typeof Buffer !== "undefined") {
207
+ return new Uint8Array(Buffer.from(base64, "base64"));
208
+ } else {
209
+ const binary = atob(base64);
210
+ const bytes = new Uint8Array(binary.length);
211
+ for (let i = 0; i < binary.length; i++) {
212
+ bytes[i] = binary.charCodeAt(i);
213
+ }
214
+ return bytes;
215
+ }
216
+ }
217
+
218
+ // lib/core/StatusList.ts
219
+ var StatusList = class _StatusList {
220
+ statusArray;
221
+ bitsPerStatus;
222
+ /**
223
+ * Creates a StatusList from a byte array.
224
+ *
225
+ * @param statusArray - Byte array containing packed status values
226
+ * @param bitsPerStatus - Number of bits per status entry
227
+ *
228
+ * @private Use static factory methods instead: fromArray(), decompressFromBase64URL(), decompressFromBytes()
229
+ */
230
+ constructor(statusArray, bitsPerStatus) {
231
+ this.statusArray = statusArray;
232
+ this.bitsPerStatus = bitsPerStatus;
233
+ }
234
+ /**
235
+ * Gets the status value at a specific index.
236
+ *
237
+ * @param index - Index of the status entry (0-based)
238
+ * @returns Status value at the given index
239
+ * @throws {IndexOutOfBoundsError} If index is out of bounds
240
+ *
241
+ * @example
242
+ * ```typescript
243
+ * const status = list.getStatus(42);
244
+ * if (status === StandardStatusValues.INVALID) {
245
+ * console.log('Credential is revoked');
246
+ * }
247
+ * ```
248
+ */
249
+ getStatus(index) {
250
+ return getBitValue(this.statusArray, index, this.bitsPerStatus);
251
+ }
252
+ /**
253
+ * Sets the status value at a specific index.
254
+ *
255
+ * Note: This modifies the status list in place. After modifying statuses,
256
+ * you'll need to recompress and re-sign the status list token.
257
+ *
258
+ * @param index - Index of the status entry (0-based)
259
+ * @param value - New status value
260
+ * @throws {IndexOutOfBoundsError} If index is out of bounds
261
+ * @throws {InvalidStatusValueError} If value exceeds the maximum for the bit size
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * // Revoke credential at index 42
266
+ * list.setStatus(42, StandardStatusValues.INVALID);
267
+ *
268
+ * // Suspend credential at index 100
269
+ * list.setStatus(100, StandardStatusValues.SUSPENDED);
270
+ * ```
271
+ */
272
+ setStatus(index, value) {
273
+ setBitValue(this.statusArray, index, value, this.bitsPerStatus);
274
+ }
275
+ /**
276
+ * Gets the number of bits used per status entry.
277
+ *
278
+ * @returns Number of bits per status (1, 2, 4, or 8)
279
+ */
280
+ getBitsPerStatus() {
281
+ return this.bitsPerStatus;
282
+ }
283
+ /**
284
+ * Gets the total number of status entries in this list.
285
+ *
286
+ * @returns Total capacity of the status list
287
+ */
288
+ getSize() {
289
+ return calculateCapacity(this.statusArray.length, this.bitsPerStatus);
290
+ }
291
+ /**
292
+ * Gets the raw byte array (for internal use).
293
+ *
294
+ * @returns The underlying byte array
295
+ */
296
+ getBytes() {
297
+ return this.statusArray;
298
+ }
299
+ /**
300
+ * Compresses the status list to base64url format (for JWT).
301
+ *
302
+ * @returns Base64URL-encoded compressed status list
303
+ *
304
+ * @example
305
+ * ```typescript
306
+ * const lst = list.compressToBase64URL();
307
+ * // Use in JWT payload: { status_list: { bits: 1, lst } }
308
+ * ```
309
+ */
310
+ compressToBase64URL() {
311
+ return compressToBase64URL(this.statusArray);
312
+ }
313
+ /**
314
+ * Compresses the status list to raw bytes (for CWT).
315
+ *
316
+ * @returns Compressed status list as bytes
317
+ *
318
+ * @example
319
+ * ```typescript
320
+ * const lst = list.compressToBytes();
321
+ * // Use in CWT payload: { 65533: { bits: 1, lst } }
322
+ * ```
323
+ */
324
+ compressToBytes() {
325
+ return compress(this.statusArray);
326
+ }
327
+ // ===== Static Factory Methods =====
328
+ /**
329
+ * Creates a StatusList from an array of status values.
330
+ *
331
+ * @param statuses - Array of status values
332
+ * @param bitsPerStatus - Number of bits per status entry (1, 2, 4, or 8)
333
+ * @returns New StatusList instance
334
+ * @throws {InvalidBitSizeError} If bitsPerStatus is not 1, 2, 4, or 8
335
+ * @throws {InvalidStatusValueError} If any status value exceeds the maximum for the bit size
336
+ *
337
+ * @example
338
+ * ```typescript
339
+ * // Create list with 1-bit statuses (valid/invalid)
340
+ * const statuses = new Array(10000).fill(0); // All valid
341
+ * statuses[42] = 1; // Revoke one credential
342
+ * const list = StatusList.fromArray(statuses, 1);
343
+ *
344
+ * // Create list with 2-bit statuses (valid/invalid/suspended/custom)
345
+ * const statuses2 = [0, 1, 2, 0, 1, 3];
346
+ * const list2 = StatusList.fromArray(statuses2, 2);
347
+ * ```
348
+ */
349
+ static fromArray(statuses, bitsPerStatus) {
350
+ const bytes = packBits(statuses, bitsPerStatus);
351
+ return new _StatusList(bytes, bitsPerStatus);
352
+ }
353
+ /**
354
+ * Creates a StatusList by decompressing a base64url-encoded string (from JWT).
355
+ *
356
+ * @param compressed - Base64URL-encoded compressed status list
357
+ * @param bitsPerStatus - Number of bits per status entry
358
+ * @returns New StatusList instance
359
+ * @throws {CompressionError} If decompression fails
360
+ *
361
+ * @example
362
+ * ```typescript
363
+ * // From JWT payload
364
+ * const payload = parseJWT(token);
365
+ * const list = StatusList.decompressFromBase64URL(
366
+ * payload.status_list.lst,
367
+ * payload.status_list.bits
368
+ * );
369
+ * ```
370
+ */
371
+ static decompressFromBase64URL(compressed, bitsPerStatus) {
372
+ const bytes = decompressFromBase64URL(compressed);
373
+ return new _StatusList(bytes, bitsPerStatus);
374
+ }
375
+ /**
376
+ * Creates a StatusList by decompressing raw bytes (from CWT).
377
+ *
378
+ * @param compressed - Compressed status list as bytes
379
+ * @param bitsPerStatus - Number of bits per status entry
380
+ * @returns New StatusList instance
381
+ * @throws {CompressionError} If decompression fails
382
+ *
383
+ * @example
384
+ * ```typescript
385
+ * // From CWT payload
386
+ * const payload = parseCWT(token);
387
+ * const statusListData = payload[65533]; // status_list claim
388
+ * const list = StatusList.decompressFromBytes(
389
+ * statusListData.lst,
390
+ * statusListData.bits
391
+ * );
392
+ * ```
393
+ */
394
+ static decompressFromBytes(compressed, bitsPerStatus) {
395
+ const bytes = decompress(compressed);
396
+ return new _StatusList(bytes, bitsPerStatus);
397
+ }
398
+ /**
399
+ * Creates an empty StatusList with a specified capacity.
400
+ *
401
+ * All status entries are initialized to 0 (VALID).
402
+ *
403
+ * @param capacity - Number of status entries
404
+ * @param bitsPerStatus - Number of bits per status entry (1, 2, 4, or 8)
405
+ * @returns New StatusList instance
406
+ *
407
+ * @example
408
+ * ```typescript
409
+ * // Create empty list for 100,000 credentials
410
+ * const list = StatusList.create(100000, 1);
411
+ * ```
412
+ */
413
+ static create(capacity, bitsPerStatus) {
414
+ const totalBits = capacity * bitsPerStatus;
415
+ const byteLength = Math.ceil(totalBits / 8);
416
+ const bytes = new Uint8Array(byteLength);
417
+ return new _StatusList(bytes, bitsPerStatus);
418
+ }
419
+ /**
420
+ * Converts the status list to an array of status values (for debugging/testing).
421
+ *
422
+ * Warning: For large status lists, this can be memory-intensive.
423
+ *
424
+ * @returns Array of all status values
425
+ */
426
+ toArray() {
427
+ return unpackBits(this.statusArray, this.bitsPerStatus);
428
+ }
429
+ };
430
+
431
+ // lib/types/common.ts
432
+ var StandardStatusValues = /* @__PURE__ */ ((StandardStatusValues2) => {
433
+ StandardStatusValues2[StandardStatusValues2["VALID"] = 0] = "VALID";
434
+ StandardStatusValues2[StandardStatusValues2["INVALID"] = 1] = "INVALID";
435
+ StandardStatusValues2[StandardStatusValues2["SUSPENDED"] = 2] = "SUSPENDED";
436
+ StandardStatusValues2[StandardStatusValues2["APPLICATION_SPECIFIC"] = 3] = "APPLICATION_SPECIFIC";
437
+ return StandardStatusValues2;
438
+ })(StandardStatusValues || {});
439
+
440
+ // lib/formats/jwt/StatusListJWT.ts
441
+ function createJWTStatusListPayload(options) {
442
+ const {
443
+ iss,
444
+ sub,
445
+ lst,
446
+ bits,
447
+ iat = Math.floor(Date.now() / 1e3),
448
+ exp,
449
+ ttl,
450
+ aggregation_uri,
451
+ additionalClaims = {}
452
+ } = options;
453
+ const header = {
454
+ typ: "statuslist+jwt",
455
+ alg: "ES256"
456
+ // Default algorithm, can be overridden when signing
457
+ };
458
+ const payload = {
459
+ iss,
460
+ sub,
461
+ iat,
462
+ status_list: {
463
+ bits,
464
+ lst,
465
+ ...aggregation_uri && { aggregation_uri }
466
+ },
467
+ ...additionalClaims
468
+ };
469
+ if (exp !== void 0) {
470
+ payload.exp = exp;
471
+ }
472
+ if (ttl !== void 0) {
473
+ payload.ttl = ttl;
474
+ }
475
+ return { header, payload };
476
+ }
477
+ function parseJWTStatusList(jwt) {
478
+ const parts = jwt.split(".");
479
+ if (parts.length !== 3) {
480
+ throw new InvalidTokenFormatError("JWT must have 3 parts separated by dots");
481
+ }
482
+ try {
483
+ const headerJson = base64UrlDecode2(parts[0]);
484
+ const header = JSON.parse(headerJson);
485
+ if (header.typ !== "statuslist+jwt") {
486
+ throw new InvalidTokenFormatError(`Invalid typ header: expected "statuslist+jwt", got "${header.typ}"`);
487
+ }
488
+ const payloadJson = base64UrlDecode2(parts[1]);
489
+ const payload = JSON.parse(payloadJson);
490
+ validateJWTPayload(payload);
491
+ const statusList = StatusList.decompressFromBase64URL(payload.status_list.lst, payload.status_list.bits);
492
+ return {
493
+ header,
494
+ payload,
495
+ jwt,
496
+ statusList
497
+ };
498
+ } catch (error) {
499
+ if (error instanceof InvalidTokenFormatError) {
500
+ throw error;
501
+ }
502
+ throw new InvalidTokenFormatError(`Failed to parse JWT: ${error.message}`);
503
+ }
504
+ }
505
+ function extractStatusListReference(credentialJWT) {
506
+ const parts = credentialJWT.split(".");
507
+ if (parts.length !== 3) {
508
+ throw new InvalidTokenFormatError("JWT must have 3 parts");
509
+ }
510
+ try {
511
+ const payloadJson = base64UrlDecode2(parts[1]);
512
+ const payload = JSON.parse(payloadJson);
513
+ if (!payload.status || typeof payload.status !== "object") {
514
+ throw new InvalidTokenFormatError("Missing status claim in credential");
515
+ }
516
+ const status = payload.status;
517
+ if (!status.status_list || typeof status.status_list !== "object") {
518
+ throw new InvalidTokenFormatError("Missing status_list in status claim");
519
+ }
520
+ const statusList = status.status_list;
521
+ if (typeof statusList.idx !== "number" || statusList.idx < 0) {
522
+ throw new InvalidTokenFormatError("Invalid or missing idx in status_list");
523
+ }
524
+ if (typeof statusList.uri !== "string" || !statusList.uri) {
525
+ throw new InvalidTokenFormatError("Invalid or missing uri in status_list");
526
+ }
527
+ return {
528
+ idx: statusList.idx,
529
+ uri: statusList.uri
530
+ };
531
+ } catch (error) {
532
+ if (error instanceof InvalidTokenFormatError) {
533
+ throw error;
534
+ }
535
+ throw new InvalidTokenFormatError(`Failed to extract status reference: ${error.message}`);
536
+ }
537
+ }
538
+ function validateJWTPayload(payload) {
539
+ if (!payload.iss || typeof payload.iss !== "string") {
540
+ throw new InvalidTokenFormatError('Missing or invalid "iss" claim');
541
+ }
542
+ if (!payload.sub || typeof payload.sub !== "string") {
543
+ throw new InvalidTokenFormatError('Missing or invalid "sub" claim');
544
+ }
545
+ if (typeof payload.iat !== "number") {
546
+ throw new InvalidTokenFormatError('Missing or invalid "iat" claim');
547
+ }
548
+ if (!payload.status_list || typeof payload.status_list !== "object") {
549
+ throw new InvalidTokenFormatError('Missing or invalid "status_list" claim');
550
+ }
551
+ const { bits, lst } = payload.status_list;
552
+ if (![1, 2, 4, 8].includes(bits)) {
553
+ throw new InvalidTokenFormatError(`Invalid "bits" value: must be 1, 2, 4, or 8, got ${bits}`);
554
+ }
555
+ if (!lst || typeof lst !== "string") {
556
+ throw new InvalidTokenFormatError('Missing or invalid "lst" field in status_list');
557
+ }
558
+ if (payload.exp !== void 0 && typeof payload.exp !== "number") {
559
+ throw new InvalidTokenFormatError('Invalid "exp" claim: must be a number');
560
+ }
561
+ if (payload.ttl !== void 0 && typeof payload.ttl !== "number") {
562
+ throw new InvalidTokenFormatError('Invalid "ttl" claim: must be a number');
563
+ }
564
+ }
565
+ function base64UrlDecode2(base64url) {
566
+ let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
567
+ const padding = base64.length % 4;
568
+ if (padding > 0) {
569
+ base64 += "=".repeat(4 - padding);
570
+ }
571
+ if (typeof Buffer !== "undefined") {
572
+ return Buffer.from(base64, "base64").toString("utf-8");
573
+ } else {
574
+ return decodeURIComponent(
575
+ atob(base64).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
576
+ );
577
+ }
578
+ }
579
+ async function signStatusListJWT(payload, privateKey, options) {
580
+ const { alg = "ES256", kid, additionalHeaders = {} } = options || {};
581
+ const jwt = new SignJWT(payload).setProtectedHeader({
582
+ typ: "statuslist+jwt",
583
+ alg,
584
+ ...kid && { kid },
585
+ ...additionalHeaders
586
+ }).setIssuer(payload.iss).setSubject(payload.sub).setIssuedAt(payload.iat);
587
+ if (payload.exp !== void 0) {
588
+ jwt.setExpirationTime(payload.exp);
589
+ }
590
+ return await jwt.sign(privateKey);
591
+ }
592
+ async function verifyStatusListJWT(jwt, publicKey, options) {
593
+ const { issuer, subject, currentTime, clockTolerance = 0 } = options || {};
594
+ try {
595
+ const result = await jwtVerify(jwt, publicKey, {
596
+ typ: "statuslist+jwt",
597
+ ...issuer && { issuer },
598
+ ...subject && { subject },
599
+ ...currentTime && { currentDate: new Date(currentTime * 1e3) },
600
+ clockTolerance
601
+ });
602
+ const header = result.protectedHeader;
603
+ const payload = result.payload;
604
+ if (header.typ !== "statuslist+jwt") {
605
+ throw new InvalidTokenFormatError(`Invalid typ header: expected "statuslist+jwt", got "${header.typ}"`);
606
+ }
607
+ if (!payload.iss || typeof payload.iss !== "string") {
608
+ throw new InvalidTokenFormatError('Missing or invalid "iss" claim');
609
+ }
610
+ if (!payload.sub || typeof payload.sub !== "string") {
611
+ throw new InvalidTokenFormatError('Missing or invalid "sub" claim');
612
+ }
613
+ if (typeof payload.iat !== "number") {
614
+ throw new InvalidTokenFormatError('Missing or invalid "iat" claim');
615
+ }
616
+ if (!payload.status_list || typeof payload.status_list !== "object") {
617
+ throw new InvalidTokenFormatError('Missing or invalid "status_list" claim');
618
+ }
619
+ const { bits, lst } = payload.status_list;
620
+ if (![1, 2, 4, 8].includes(bits)) {
621
+ throw new InvalidTokenFormatError(`Invalid "bits" value: must be 1, 2, 4, or 8, got ${bits}`);
622
+ }
623
+ if (!lst || typeof lst !== "string") {
624
+ throw new InvalidTokenFormatError('Missing or invalid "lst" field in status_list');
625
+ }
626
+ return {
627
+ header,
628
+ payload
629
+ };
630
+ } catch (error) {
631
+ if (error instanceof InvalidTokenFormatError) {
632
+ throw error;
633
+ }
634
+ const message = error.message;
635
+ throw new InvalidTokenFormatError(`JWT verification failed: ${message}`);
636
+ }
637
+ }
638
+ async function verifySignatureOnly(jwt, publicKey) {
639
+ try {
640
+ const result = await jwtVerify(jwt, publicKey, {
641
+ typ: "statuslist+jwt"
642
+ });
643
+ return {
644
+ header: result.protectedHeader,
645
+ payload: result.payload
646
+ };
647
+ } catch (error) {
648
+ throw new InvalidTokenFormatError(`Signature verification failed: ${error.message}`);
649
+ }
650
+ }
651
+
652
+ // lib/formats/cwt/types.ts
653
+ var CWT_CLAIMS = {
654
+ /** Issuer (iss) - Claim key 1 */
655
+ ISS: 1,
656
+ /** Subject (sub) - Claim key 2 */
657
+ SUB: 2,
658
+ /** Expiration Time (exp) - Claim key 4 */
659
+ EXP: 4,
660
+ /** Issued At (iat) - Claim key 6 */
661
+ IAT: 6,
662
+ /** Time to Live (ttl) - Claim key 65534 (custom) */
663
+ TTL: 65534,
664
+ /** Status List - Claim key 65533 (custom) */
665
+ STATUS_LIST: 65533
666
+ };
667
+ var COSE_HEADERS = {
668
+ /** Algorithm - Header parameter 1 */
669
+ ALG: 1,
670
+ /** Content Type - Header parameter 16 */
671
+ CONTENT_TYPE: 16,
672
+ /** Key ID - Header parameter 4 */
673
+ KID: 4
674
+ };
675
+ var COSE_ALGORITHMS = {
676
+ /** ES256 - ECDSA with SHA-256 */
677
+ ES256: -7,
678
+ /** ES384 - ECDSA with SHA-384 */
679
+ ES384: -35,
680
+ /** ES512 - ECDSA with SHA-512 */
681
+ ES512: -36,
682
+ /** EdDSA */
683
+ EdDSA: -8
684
+ };
685
+ var COSE_SIGN1_TAG = 18;
686
+ var ALGORITHM_MAP = {
687
+ "-7": { hash: "sha256", curve: "prime256v1" },
688
+ // ES256
689
+ "-35": { hash: "sha384", curve: "secp384r1" },
690
+ // ES384
691
+ "-36": { hash: "sha512", curve: "secp521r1" },
692
+ // ES512
693
+ "-8": { hash: "sha512" }
694
+ // EdDSA (Ed25519)
695
+ };
696
+ function signCOSE(payload, protectedHeader, privateKey) {
697
+ const protectedHeaderEncoded = cbor.encode(protectedHeader);
698
+ const sigStructure = cbor.encode([
699
+ "Signature1",
700
+ // Context
701
+ protectedHeaderEncoded,
702
+ new Uint8Array(0),
703
+ // External AAD (empty)
704
+ payload
705
+ ]);
706
+ const alg = protectedHeader.get(1);
707
+ if (!alg) {
708
+ throw new InvalidTokenFormatError("Algorithm (alg) must be specified in protected header");
709
+ }
710
+ const signature = signData(sigStructure, alg, privateKey);
711
+ const coseSign1 = [
712
+ protectedHeaderEncoded,
713
+ // Protected header (encoded)
714
+ {},
715
+ // Unprotected header (empty map)
716
+ payload,
717
+ // Payload
718
+ signature
719
+ // Signature
720
+ ];
721
+ const tagged = new cbor.Tagged(COSE_SIGN1_TAG, coseSign1);
722
+ return cbor.encode(tagged);
723
+ }
724
+ function verifyCOSE(coseSign1, publicKey) {
725
+ try {
726
+ const decoded = cbor.decode(coseSign1);
727
+ if (!(decoded instanceof cbor.Tagged) || decoded.tag !== COSE_SIGN1_TAG) {
728
+ throw new InvalidTokenFormatError("Invalid COSE Sign1 structure: missing tag 18");
729
+ }
730
+ const [protectedHeaderEncoded, , payload, signature] = decoded.value;
731
+ const protectedHeader = cbor.decode(protectedHeaderEncoded);
732
+ const alg = protectedHeader.get(1);
733
+ if (!alg) {
734
+ throw new InvalidTokenFormatError("Missing algorithm in protected header");
735
+ }
736
+ const sigStructure = cbor.encode([
737
+ "Signature1",
738
+ protectedHeaderEncoded,
739
+ new Uint8Array(0),
740
+ // External AAD
741
+ payload
742
+ ]);
743
+ const valid = verifySignature(sigStructure, signature, alg, publicKey);
744
+ if (!valid) {
745
+ throw new InvalidTokenFormatError("Invalid COSE Sign1 signature");
746
+ }
747
+ return payload;
748
+ } catch (error) {
749
+ if (error instanceof InvalidTokenFormatError) {
750
+ throw error;
751
+ }
752
+ throw new InvalidTokenFormatError(`Failed to verify COSE Sign1: ${error.message}`);
753
+ }
754
+ }
755
+ function signData(data, alg, privateKey) {
756
+ const algInfo = ALGORITHM_MAP[alg.toString()];
757
+ if (!algInfo) {
758
+ throw new InvalidTokenFormatError(`Unsupported algorithm: ${alg}`);
759
+ }
760
+ if (privateKey.kty === "EC2") {
761
+ if (!privateKey.d) {
762
+ throw new InvalidTokenFormatError("Private key (d) is required for signing");
763
+ }
764
+ const pemKey = ec2KeyToPEM();
765
+ const sign = createSign(`RSA-SHA${algInfo.hash.replace("sha", "")}`);
766
+ sign.update(data);
767
+ const signature = sign.sign(pemKey);
768
+ return derToRaw(signature, getSignatureLength(alg));
769
+ } else if (privateKey.kty === "OKP") {
770
+ if (!privateKey.d) {
771
+ throw new InvalidTokenFormatError("Private key (d) is required for signing");
772
+ }
773
+ const pemKey = okpKeyToPEM();
774
+ const sign = createSign("SHA512");
775
+ sign.update(data);
776
+ return sign.sign(pemKey);
777
+ }
778
+ const _exhaustiveCheck = privateKey;
779
+ throw new InvalidTokenFormatError(`Unsupported key type: ${_exhaustiveCheck.kty}`);
780
+ }
781
+ function verifySignature(data, signature, alg, publicKey) {
782
+ const algInfo = ALGORITHM_MAP[alg.toString()];
783
+ if (!algInfo) {
784
+ throw new InvalidTokenFormatError(`Unsupported algorithm: ${alg}`);
785
+ }
786
+ if (publicKey.kty === "EC2") {
787
+ const pemKey = ec2KeyToPEM();
788
+ const derSignature = rawToDer(signature);
789
+ const verify = createVerify(`RSA-SHA${algInfo.hash.replace("sha", "")}`);
790
+ verify.update(data);
791
+ return verify.verify(pemKey, derSignature);
792
+ } else if (publicKey.kty === "OKP") {
793
+ const pemKey = okpKeyToPEM();
794
+ const verify = createVerify("SHA512");
795
+ verify.update(data);
796
+ return verify.verify(pemKey, signature);
797
+ }
798
+ const _exhaustiveCheck = publicKey;
799
+ throw new InvalidTokenFormatError(`Unsupported key type: ${_exhaustiveCheck.kty}`);
800
+ }
801
+ function ec2KeyToPEM(_key, _isPrivate) {
802
+ throw new Error("EC2 to PEM conversion not fully implemented. Use a COSE library like @transmute/cose instead.");
803
+ }
804
+ function okpKeyToPEM(_key, _isPrivate) {
805
+ throw new Error("OKP to PEM conversion not fully implemented. Use a COSE library like @transmute/cose instead.");
806
+ }
807
+ function derToRaw(derSignature, length) {
808
+ let offset = 2;
809
+ const rLength = derSignature[offset + 1];
810
+ offset += 2;
811
+ const r = derSignature.slice(offset, offset + rLength);
812
+ offset += rLength;
813
+ const sLength = derSignature[offset + 1];
814
+ offset += 2;
815
+ const s = derSignature.slice(offset, offset + sLength);
816
+ const halfLength = length / 2;
817
+ const raw = new Uint8Array(length);
818
+ raw.set(r.slice(-halfLength), 0);
819
+ raw.set(s.slice(-halfLength), halfLength);
820
+ return raw;
821
+ }
822
+ function rawToDer(rawSignature) {
823
+ const halfLength = rawSignature.length / 2;
824
+ const r = rawSignature.slice(0, halfLength);
825
+ const s = rawSignature.slice(halfLength);
826
+ const derR = encodeDerInteger(r);
827
+ const derS = encodeDerInteger(s);
828
+ const sequence = new Uint8Array(2 + derR.length + derS.length);
829
+ sequence[0] = 48;
830
+ sequence[1] = derR.length + derS.length;
831
+ sequence.set(derR, 2);
832
+ sequence.set(derS, 2 + derR.length);
833
+ return sequence;
834
+ }
835
+ function encodeDerInteger(value) {
836
+ let i = 0;
837
+ while (i < value.length && value[i] === 0) {
838
+ i++;
839
+ }
840
+ value = value.slice(i);
841
+ const needsLeadingZero = value[0] >= 128;
842
+ const length = value.length + (needsLeadingZero ? 1 : 0);
843
+ const der = new Uint8Array(2 + length);
844
+ der[0] = 2;
845
+ der[1] = length;
846
+ if (needsLeadingZero) {
847
+ der[2] = 0;
848
+ der.set(value, 3);
849
+ } else {
850
+ der.set(value, 2);
851
+ }
852
+ return der;
853
+ }
854
+ function getSignatureLength(alg) {
855
+ switch (alg) {
856
+ case -7:
857
+ return 64;
858
+ case -35:
859
+ return 96;
860
+ case -36:
861
+ return 132;
862
+ case -8:
863
+ return 64;
864
+ default:
865
+ throw new InvalidTokenFormatError(`Unknown signature length for algorithm: ${alg}`);
866
+ }
867
+ }
868
+
869
+ // lib/formats/cwt/StatusListCWT.ts
870
+ function createCWTStatusListPayload(options) {
871
+ const {
872
+ iss,
873
+ sub,
874
+ lst,
875
+ bits,
876
+ iat = Math.floor(Date.now() / 1e3),
877
+ exp,
878
+ ttl,
879
+ aggregation_uri,
880
+ additionalClaims = {}
881
+ } = options;
882
+ const payload = {
883
+ ...additionalClaims,
884
+ [CWT_CLAIMS.STATUS_LIST]: {
885
+ bits,
886
+ lst,
887
+ ...aggregation_uri && { aggregation_uri }
888
+ }
889
+ };
890
+ if (iss !== void 0) {
891
+ payload[CWT_CLAIMS.ISS] = iss;
892
+ }
893
+ if (sub !== void 0) {
894
+ payload[CWT_CLAIMS.SUB] = sub;
895
+ }
896
+ if (iat !== void 0) {
897
+ payload[CWT_CLAIMS.IAT] = iat;
898
+ }
899
+ if (exp !== void 0) {
900
+ payload[CWT_CLAIMS.EXP] = exp;
901
+ }
902
+ if (ttl !== void 0) {
903
+ payload[CWT_CLAIMS.TTL] = ttl;
904
+ }
905
+ return payload;
906
+ }
907
+ function encodeCWTPayload(payload) {
908
+ return cbor.encode(payload);
909
+ }
910
+ function parseCWTStatusList(cwtBytes) {
911
+ try {
912
+ const payload = cbor.decode(cwtBytes);
913
+ validateCWTPayload(payload);
914
+ const typedPayload = payload;
915
+ const statusListClaim = typedPayload[CWT_CLAIMS.STATUS_LIST];
916
+ const statusList = StatusList.decompressFromBytes(statusListClaim.lst, statusListClaim.bits);
917
+ return {
918
+ protectedHeader: /* @__PURE__ */ new Map(),
919
+ unprotectedHeader: /* @__PURE__ */ new Map(),
920
+ payload: typedPayload,
921
+ cwt: cwtBytes,
922
+ statusList
923
+ };
924
+ } catch (error) {
925
+ if (error instanceof InvalidTokenFormatError) {
926
+ throw error;
927
+ }
928
+ throw new InvalidTokenFormatError(`Failed to parse CWT: ${error.message}`);
929
+ }
930
+ }
931
+ function parseCWTStatusListSigned(cwtBytes, publicKey) {
932
+ try {
933
+ const payloadBytes = verifyCOSE(cwtBytes, publicKey);
934
+ const payload = cbor.decode(payloadBytes);
935
+ validateCWTPayload(payload);
936
+ const typedPayload = payload;
937
+ const statusListClaim = typedPayload[CWT_CLAIMS.STATUS_LIST];
938
+ const statusList = StatusList.decompressFromBytes(statusListClaim.lst, statusListClaim.bits);
939
+ const decoded = cbor.decode(cwtBytes);
940
+ const [protectedHeaderEncoded] = decoded.value;
941
+ const protectedHeader = cbor.decode(protectedHeaderEncoded);
942
+ return {
943
+ protectedHeader,
944
+ unprotectedHeader: /* @__PURE__ */ new Map(),
945
+ payload: typedPayload,
946
+ cwt: cwtBytes,
947
+ statusList
948
+ };
949
+ } catch (error) {
950
+ if (error instanceof InvalidTokenFormatError) {
951
+ throw error;
952
+ }
953
+ throw new InvalidTokenFormatError(`Failed to parse signed CWT: ${error.message}`);
954
+ }
955
+ }
956
+ function signCWTStatusList(payload, privateKey, options) {
957
+ const { alg = COSE_ALGORITHMS.ES256, kid, additionalHeaders = /* @__PURE__ */ new Map() } = options || {};
958
+ const protectedHeader = new Map([
959
+ [COSE_HEADERS.CONTENT_TYPE, "application/statuslist+cwt"],
960
+ [COSE_HEADERS.ALG, alg],
961
+ ...additionalHeaders
962
+ ]);
963
+ if (kid) {
964
+ protectedHeader.set(COSE_HEADERS.KID, kid);
965
+ }
966
+ const payloadBytes = cbor.encode(payload);
967
+ return signCOSE(payloadBytes, protectedHeader, privateKey);
968
+ }
969
+ function extractStatusListReferenceCBOR(credentialCBOR) {
970
+ try {
971
+ const credential = cbor.decode(credentialCBOR);
972
+ const status = credential.status || credential["status"];
973
+ if (!status || typeof status !== "object") {
974
+ throw new InvalidTokenFormatError("Missing status claim in credential");
975
+ }
976
+ const statusObj = status;
977
+ const statusList = statusObj.status_list || statusObj["status_list"];
978
+ if (!statusList || typeof statusList !== "object") {
979
+ throw new InvalidTokenFormatError("Missing status_list in status claim");
980
+ }
981
+ const statusListObj = statusList;
982
+ if (typeof statusListObj.idx !== "number" || statusListObj.idx < 0) {
983
+ throw new InvalidTokenFormatError("Invalid or missing idx in status_list");
984
+ }
985
+ if (typeof statusListObj.uri !== "string" || !statusListObj.uri) {
986
+ throw new InvalidTokenFormatError("Invalid or missing uri in status_list");
987
+ }
988
+ return {
989
+ idx: statusListObj.idx,
990
+ uri: statusListObj.uri
991
+ };
992
+ } catch (error) {
993
+ if (error instanceof InvalidTokenFormatError) {
994
+ throw error;
995
+ }
996
+ throw new InvalidTokenFormatError(`Failed to extract status reference: ${error.message}`);
997
+ }
998
+ }
999
+ function validateCWTPayload(payload) {
1000
+ const statusList = payload[CWT_CLAIMS.STATUS_LIST];
1001
+ if (!statusList || typeof statusList !== "object") {
1002
+ throw new InvalidTokenFormatError(`Missing or invalid status_list claim (key ${CWT_CLAIMS.STATUS_LIST})`);
1003
+ }
1004
+ const statusListObj = statusList;
1005
+ if (!statusListObj.bits || ![1, 2, 4, 8].includes(statusListObj.bits)) {
1006
+ throw new InvalidTokenFormatError(`Invalid "bits" value: must be 1, 2, 4, or 8`);
1007
+ }
1008
+ if (!statusListObj.lst || !(statusListObj.lst instanceof Uint8Array)) {
1009
+ throw new InvalidTokenFormatError('Missing or invalid "lst" field in status_list');
1010
+ }
1011
+ if (payload[CWT_CLAIMS.ISS] !== void 0 && typeof payload[CWT_CLAIMS.ISS] !== "string") {
1012
+ throw new InvalidTokenFormatError(`Invalid "iss" claim (key ${CWT_CLAIMS.ISS}): must be a string`);
1013
+ }
1014
+ if (payload[CWT_CLAIMS.SUB] !== void 0 && typeof payload[CWT_CLAIMS.SUB] !== "string") {
1015
+ throw new InvalidTokenFormatError(`Invalid "sub" claim (key ${CWT_CLAIMS.SUB}): must be a string`);
1016
+ }
1017
+ if (payload[CWT_CLAIMS.IAT] !== void 0 && typeof payload[CWT_CLAIMS.IAT] !== "number") {
1018
+ throw new InvalidTokenFormatError(`Invalid "iat" claim (key ${CWT_CLAIMS.IAT}): must be a number`);
1019
+ }
1020
+ if (payload[CWT_CLAIMS.EXP] !== void 0 && typeof payload[CWT_CLAIMS.EXP] !== "number") {
1021
+ throw new InvalidTokenFormatError(`Invalid "exp" claim (key ${CWT_CLAIMS.EXP}): must be a number`);
1022
+ }
1023
+ if (payload[CWT_CLAIMS.TTL] !== void 0 && typeof payload[CWT_CLAIMS.TTL] !== "number") {
1024
+ throw new InvalidTokenFormatError(`Invalid "ttl" claim (key ${CWT_CLAIMS.TTL}): must be a number`);
1025
+ }
1026
+ }
1027
+
1028
+ // lib/helper/fetcher.ts
1029
+ var DEFAULT_TIMEOUT = 1e4;
1030
+ var DEFAULT_MAX_REDIRECTS = 3;
1031
+ var CONTENT_TYPES = {
1032
+ JWT: "application/statuslist+jwt",
1033
+ CWT: "application/statuslist+cwt"
1034
+ };
1035
+ async function fetchStatusListToken(uri, options = {}) {
1036
+ if (!uri || typeof uri !== "string") {
1037
+ throw new MissingStatusListUriError("Status list URI is required");
1038
+ }
1039
+ let parsedUri;
1040
+ try {
1041
+ parsedUri = new URL(uri);
1042
+ } catch (error) {
1043
+ throw new MissingStatusListUriError(`Invalid status list URI: ${uri}`);
1044
+ }
1045
+ if (!["http:", "https:"].includes(parsedUri.protocol)) {
1046
+ throw new FetchError(`Unsupported protocol: ${parsedUri.protocol}. Only HTTP(S) is allowed.`);
1047
+ }
1048
+ const {
1049
+ timeout = DEFAULT_TIMEOUT,
1050
+ headers = {},
1051
+ maxRedirects = DEFAULT_MAX_REDIRECTS,
1052
+ fetchImpl = fetch
1053
+ } = options;
1054
+ const controller = new AbortController();
1055
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1056
+ try {
1057
+ const response = await fetchImpl(uri, {
1058
+ method: "GET",
1059
+ headers: {
1060
+ Accept: `${CONTENT_TYPES.JWT}, ${CONTENT_TYPES.CWT}`,
1061
+ ...headers
1062
+ },
1063
+ signal: controller.signal,
1064
+ redirect: "follow",
1065
+ // @ts-expect-error - maxRedirects is not in standard fetch but supported by undici
1066
+ maxRedirects
1067
+ });
1068
+ clearTimeout(timeoutId);
1069
+ if (!response.ok) {
1070
+ throw new FetchError(
1071
+ `HTTP ${response.status} ${response.statusText} while fetching status list from ${uri}`
1072
+ );
1073
+ }
1074
+ const contentType = response.headers.get("content-type");
1075
+ if (contentType?.includes(CONTENT_TYPES.JWT)) {
1076
+ return await response.text();
1077
+ }
1078
+ if (contentType?.includes(CONTENT_TYPES.CWT)) {
1079
+ const arrayBuffer2 = await response.arrayBuffer();
1080
+ return new Uint8Array(arrayBuffer2);
1081
+ }
1082
+ const arrayBuffer = await response.arrayBuffer();
1083
+ const bytes = new Uint8Array(arrayBuffer);
1084
+ const text = new TextDecoder().decode(bytes.slice(0, 3));
1085
+ if (text === "eyJ") {
1086
+ return new TextDecoder().decode(bytes);
1087
+ }
1088
+ return bytes;
1089
+ } catch (error) {
1090
+ clearTimeout(timeoutId);
1091
+ if (error instanceof FetchError || error instanceof MissingStatusListUriError) {
1092
+ throw error;
1093
+ }
1094
+ if (error.name === "AbortError") {
1095
+ throw new FetchError(`Request timeout after ${timeout}ms while fetching ${uri}`);
1096
+ }
1097
+ throw new FetchError(
1098
+ `Failed to fetch status list from ${uri}: ${error.message}`
1099
+ );
1100
+ }
1101
+ }
1102
+ function isValidStatusListUri(uri) {
1103
+ if (!uri || typeof uri !== "string") {
1104
+ return false;
1105
+ }
1106
+ try {
1107
+ const parsed = new URL(uri);
1108
+ return ["http:", "https:"].includes(parsed.protocol);
1109
+ } catch {
1110
+ return false;
1111
+ }
1112
+ }
1113
+
1114
+ // lib/helper/validator.ts
1115
+ var MAX_TTL_SECONDS = 31536e3;
1116
+ var MIN_TTL_SECONDS = 60;
1117
+ function validateJWTPayload2(payload) {
1118
+ const errors = [];
1119
+ if (!payload.iss || typeof payload.iss !== "string") {
1120
+ errors.push('Missing or invalid "iss" claim (must be a non-empty string)');
1121
+ }
1122
+ if (!payload.sub || typeof payload.sub !== "string") {
1123
+ errors.push('Missing or invalid "sub" claim (must be a non-empty string)');
1124
+ }
1125
+ if (typeof payload.iat !== "number") {
1126
+ errors.push('Missing or invalid "iat" claim (must be a number)');
1127
+ } else if (payload.iat < 0) {
1128
+ errors.push('"iat" claim must be a positive number');
1129
+ }
1130
+ if (!payload.status_list || typeof payload.status_list !== "object") {
1131
+ errors.push('Missing or invalid "status_list" claim (must be an object)');
1132
+ } else {
1133
+ const { bits, lst } = payload.status_list;
1134
+ if (![1, 2, 4, 8].includes(bits)) {
1135
+ errors.push('"status_list.bits" must be 1, 2, 4, or 8');
1136
+ }
1137
+ if (!lst || typeof lst !== "string") {
1138
+ errors.push('"status_list.lst" must be a non-empty base64url-encoded string');
1139
+ }
1140
+ if (payload.status_list.aggregation_uri !== void 0) {
1141
+ if (typeof payload.status_list.aggregation_uri !== "string") {
1142
+ errors.push('"status_list.aggregation_uri" must be a string');
1143
+ }
1144
+ }
1145
+ }
1146
+ if (payload.exp !== void 0) {
1147
+ if (typeof payload.exp !== "number") {
1148
+ errors.push('"exp" claim must be a number');
1149
+ } else if (payload.exp < 0) {
1150
+ errors.push('"exp" claim must be a positive number');
1151
+ }
1152
+ }
1153
+ if (payload.ttl !== void 0) {
1154
+ if (typeof payload.ttl !== "number") {
1155
+ errors.push('"ttl" claim must be a number');
1156
+ } else if (payload.ttl < 0) {
1157
+ errors.push('"ttl" claim must be a positive number');
1158
+ }
1159
+ }
1160
+ return {
1161
+ valid: errors.length === 0,
1162
+ errors
1163
+ };
1164
+ }
1165
+ function validateCWTPayload2(payload) {
1166
+ const errors = [];
1167
+ const statusList = payload[CWT_CLAIMS.STATUS_LIST];
1168
+ if (!statusList || typeof statusList !== "object") {
1169
+ errors.push(`Missing or invalid status_list claim (key ${CWT_CLAIMS.STATUS_LIST})`);
1170
+ } else {
1171
+ const { bits, lst } = statusList;
1172
+ if (![1, 2, 4, 8].includes(bits)) {
1173
+ errors.push('"status_list.bits" must be 1, 2, 4, or 8');
1174
+ }
1175
+ if (!(lst instanceof Uint8Array)) {
1176
+ errors.push('"status_list.lst" must be a Uint8Array');
1177
+ } else if (lst.length === 0) {
1178
+ errors.push('"status_list.lst" must not be empty');
1179
+ }
1180
+ if (statusList.aggregation_uri !== void 0) {
1181
+ if (typeof statusList.aggregation_uri !== "string") {
1182
+ errors.push('"status_list.aggregation_uri" must be a string');
1183
+ }
1184
+ }
1185
+ }
1186
+ if (payload[CWT_CLAIMS.ISS] !== void 0) {
1187
+ if (typeof payload[CWT_CLAIMS.ISS] !== "string") {
1188
+ errors.push(`"iss" claim (key ${CWT_CLAIMS.ISS}) must be a string`);
1189
+ }
1190
+ }
1191
+ if (payload[CWT_CLAIMS.SUB] !== void 0) {
1192
+ if (typeof payload[CWT_CLAIMS.SUB] !== "string") {
1193
+ errors.push(`"sub" claim (key ${CWT_CLAIMS.SUB}) must be a string`);
1194
+ }
1195
+ }
1196
+ if (payload[CWT_CLAIMS.EXP] !== void 0) {
1197
+ const exp = payload[CWT_CLAIMS.EXP];
1198
+ if (typeof exp !== "number") {
1199
+ errors.push(`"exp" claim (key ${CWT_CLAIMS.EXP}) must be a number`);
1200
+ } else if (exp < 0) {
1201
+ errors.push(`"exp" claim must be a positive number`);
1202
+ }
1203
+ }
1204
+ if (payload[CWT_CLAIMS.IAT] !== void 0) {
1205
+ const iat = payload[CWT_CLAIMS.IAT];
1206
+ if (typeof iat !== "number") {
1207
+ errors.push(`"iat" claim (key ${CWT_CLAIMS.IAT}) must be a number`);
1208
+ } else if (iat < 0) {
1209
+ errors.push(`"iat" claim must be a positive number`);
1210
+ }
1211
+ }
1212
+ if (payload[CWT_CLAIMS.TTL] !== void 0) {
1213
+ const ttl = payload[CWT_CLAIMS.TTL];
1214
+ if (typeof ttl !== "number") {
1215
+ errors.push(`"ttl" claim (key ${CWT_CLAIMS.TTL}) must be a number`);
1216
+ } else if (ttl < 0) {
1217
+ errors.push(`"ttl" claim must be a positive number`);
1218
+ }
1219
+ }
1220
+ return {
1221
+ valid: errors.length === 0,
1222
+ errors
1223
+ };
1224
+ }
1225
+ function validateExpiry(exp, iat, ttl, currentTime = Math.floor(Date.now() / 1e3)) {
1226
+ const errors = [];
1227
+ if (iat !== void 0 && iat > currentTime) {
1228
+ errors.push(`Token issued in the future (iat: ${iat}, current: ${currentTime})`);
1229
+ }
1230
+ if (exp !== void 0) {
1231
+ if (exp < currentTime) {
1232
+ errors.push(`Token expired (exp: ${exp}, current: ${currentTime})`);
1233
+ }
1234
+ }
1235
+ if (ttl !== void 0 && iat !== void 0) {
1236
+ const expirationTime = iat + ttl;
1237
+ if (expirationTime < currentTime) {
1238
+ errors.push(`Token expired by ttl (iat: ${iat}, ttl: ${ttl}, current: ${currentTime})`);
1239
+ }
1240
+ }
1241
+ if (exp !== void 0 && ttl !== void 0 && iat !== void 0) {
1242
+ const calculatedExp = iat + ttl;
1243
+ if (Math.abs(exp - calculatedExp) > 1) {
1244
+ errors.push(
1245
+ `Inconsistent exp and ttl values (exp: ${exp}, iat + ttl: ${calculatedExp})`
1246
+ );
1247
+ }
1248
+ }
1249
+ return {
1250
+ valid: errors.length === 0,
1251
+ errors
1252
+ };
1253
+ }
1254
+ function validateTTLBounds(ttl) {
1255
+ const errors = [];
1256
+ if (ttl === void 0) {
1257
+ return { valid: true, errors: [] };
1258
+ }
1259
+ if (ttl < MIN_TTL_SECONDS) {
1260
+ errors.push(
1261
+ `TTL too short (${ttl} seconds). Recommended minimum: ${MIN_TTL_SECONDS} seconds (1 minute)`
1262
+ );
1263
+ }
1264
+ if (ttl > MAX_TTL_SECONDS) {
1265
+ errors.push(
1266
+ `TTL too large (${ttl} seconds). Recommended maximum: ${MAX_TTL_SECONDS} seconds (1 year). Excessively large TTL values may be used for DoS attacks.`
1267
+ );
1268
+ }
1269
+ return {
1270
+ valid: errors.length === 0,
1271
+ errors
1272
+ };
1273
+ }
1274
+ function isExpired(exp, iat, ttl, currentTime = Math.floor(Date.now() / 1e3)) {
1275
+ if (exp !== void 0 && exp < currentTime) {
1276
+ return true;
1277
+ }
1278
+ if (ttl !== void 0 && iat !== void 0) {
1279
+ const expirationTime = iat + ttl;
1280
+ if (expirationTime < currentTime) {
1281
+ return true;
1282
+ }
1283
+ }
1284
+ return false;
1285
+ }
1286
+
1287
+ // lib/helper/StatusListTokenHelper.ts
1288
+ var StatusListTokenHelper = class _StatusListTokenHelper {
1289
+ constructor(header, payload, statusList, format) {
1290
+ this.header = header;
1291
+ this.payload = payload;
1292
+ this.statusList = statusList;
1293
+ this.format = format;
1294
+ }
1295
+ /**
1296
+ * Creates a helper from a Status List Token (JWT or CWT).
1297
+ *
1298
+ * Automatically detects the format:
1299
+ * - JWT: starts with "eyJ" (base64url-encoded header)
1300
+ * - CWT: CBOR binary format
1301
+ *
1302
+ * @param token - JWT string or CWT Uint8Array
1303
+ * @returns StatusListTokenHelper instance
1304
+ * @throws {InvalidTokenFormatError} If token format is invalid
1305
+ *
1306
+ * @example
1307
+ * ```typescript
1308
+ * // From JWT
1309
+ * const helper = StatusListTokenHelper.fromToken(jwtString);
1310
+ *
1311
+ * // From CWT
1312
+ * const helper = StatusListTokenHelper.fromToken(cwtBytes);
1313
+ * ```
1314
+ */
1315
+ static fromToken(token) {
1316
+ if (typeof token === "string") {
1317
+ const { header, payload: payload2, statusList: statusList2 } = parseJWTStatusList(token);
1318
+ return new _StatusListTokenHelper(header, payload2, statusList2, "jwt");
1319
+ }
1320
+ const { protectedHeader, payload, statusList } = parseCWTStatusList(token);
1321
+ return new _StatusListTokenHelper(protectedHeader, payload, statusList, "cwt");
1322
+ }
1323
+ /**
1324
+ * Creates a helper by fetching a Status List Token from a URI.
1325
+ *
1326
+ * Fetches the token via HTTP GET and automatically detects the format.
1327
+ *
1328
+ * @param reference - Status reference containing idx and uri
1329
+ * @param options - Fetch options
1330
+ * @returns StatusListTokenHelper instance
1331
+ * @throws {FetchError} If HTTP request fails
1332
+ * @throws {InvalidTokenFormatError} If token format is invalid
1333
+ *
1334
+ * @example
1335
+ * ```typescript
1336
+ * const reference = {
1337
+ * idx: 42,
1338
+ * uri: 'https://issuer.example.com/status/1'
1339
+ * };
1340
+ *
1341
+ * const helper = await StatusListTokenHelper.fromStatusReference(reference);
1342
+ * const status = helper.getStatus(reference.idx);
1343
+ * ```
1344
+ */
1345
+ static async fromStatusReference(reference, options) {
1346
+ const token = await fetchStatusListToken(reference.uri, options);
1347
+ return _StatusListTokenHelper.fromToken(token);
1348
+ }
1349
+ /**
1350
+ * Gets the status value at a specific index.
1351
+ *
1352
+ * @param index - Index in the status list
1353
+ * @returns Status value (0-255 depending on bits per status)
1354
+ * @throws {IndexOutOfBoundsError} If index is out of bounds
1355
+ *
1356
+ * @example
1357
+ * ```typescript
1358
+ * const status = helper.getStatus(42);
1359
+ * if (status === StandardStatusValues.INVALID) {
1360
+ * console.log('Credential 42 is revoked');
1361
+ * }
1362
+ * ```
1363
+ */
1364
+ getStatus(index) {
1365
+ return this.statusList.getStatus(index);
1366
+ }
1367
+ /**
1368
+ * Checks if the status list token is expired.
1369
+ *
1370
+ * A token is expired if:
1371
+ * - exp is set and is in the past, OR
1372
+ * - ttl is set and iat + ttl is in the past
1373
+ *
1374
+ * @param currentTime - Current time (seconds since epoch). Defaults to now.
1375
+ * @returns True if expired
1376
+ *
1377
+ * @example
1378
+ * ```typescript
1379
+ * if (helper.isExpired()) {
1380
+ * throw new Error('Status list expired - fetch a new one');
1381
+ * }
1382
+ * ```
1383
+ */
1384
+ isExpired(currentTime) {
1385
+ return isExpired(this.exp, this.iat, this.ttl, currentTime);
1386
+ }
1387
+ /**
1388
+ * Gets the issuer identifier (iss claim).
1389
+ *
1390
+ * @returns Issuer string or undefined if not set
1391
+ */
1392
+ get iss() {
1393
+ if (this.format === "jwt") {
1394
+ return this.payload.iss;
1395
+ }
1396
+ return this.payload[CWT_CLAIMS.ISS];
1397
+ }
1398
+ /**
1399
+ * Gets the subject identifier (sub claim).
1400
+ *
1401
+ * @returns Subject string or undefined if not set
1402
+ */
1403
+ get sub() {
1404
+ if (this.format === "jwt") {
1405
+ return this.payload.sub;
1406
+ }
1407
+ return this.payload[CWT_CLAIMS.SUB];
1408
+ }
1409
+ /**
1410
+ * Gets the issued at time (iat claim).
1411
+ *
1412
+ * @returns Timestamp in seconds since epoch, or undefined if not set
1413
+ */
1414
+ get iat() {
1415
+ if (this.format === "jwt") {
1416
+ return this.payload.iat;
1417
+ }
1418
+ return this.payload[CWT_CLAIMS.IAT];
1419
+ }
1420
+ /**
1421
+ * Gets the expiration time (exp claim).
1422
+ *
1423
+ * @returns Timestamp in seconds since epoch, or undefined if not set
1424
+ */
1425
+ get exp() {
1426
+ if (this.format === "jwt") {
1427
+ return this.payload.exp;
1428
+ }
1429
+ return this.payload[CWT_CLAIMS.EXP];
1430
+ }
1431
+ /**
1432
+ * Gets the time to live (ttl claim).
1433
+ *
1434
+ * @returns TTL in seconds, or undefined if not set
1435
+ */
1436
+ get ttl() {
1437
+ if (this.format === "jwt") {
1438
+ return this.payload.ttl;
1439
+ }
1440
+ return this.payload[CWT_CLAIMS.TTL];
1441
+ }
1442
+ /**
1443
+ * Gets the number of bits per status entry.
1444
+ *
1445
+ * @returns Bits per status (1, 2, 4, or 8)
1446
+ */
1447
+ get bits() {
1448
+ if (this.format === "jwt") {
1449
+ return this.payload.status_list.bits;
1450
+ }
1451
+ return this.payload[CWT_CLAIMS.STATUS_LIST].bits;
1452
+ }
1453
+ /**
1454
+ * Gets the aggregation URI if set.
1455
+ *
1456
+ * @returns Aggregation URI or undefined if not set
1457
+ */
1458
+ get aggregationUri() {
1459
+ if (this.format === "jwt") {
1460
+ return this.payload.status_list.aggregation_uri;
1461
+ }
1462
+ return this.payload[CWT_CLAIMS.STATUS_LIST].aggregation_uri;
1463
+ }
1464
+ /**
1465
+ * Gets the format of the token.
1466
+ *
1467
+ * @returns 'jwt' or 'cwt'
1468
+ */
1469
+ get tokenFormat() {
1470
+ return this.format;
1471
+ }
1472
+ /**
1473
+ * Gets the size of the status list.
1474
+ *
1475
+ * @returns Number of status entries
1476
+ */
1477
+ get size() {
1478
+ return this.statusList.getSize();
1479
+ }
1480
+ /**
1481
+ * Gets the raw header.
1482
+ *
1483
+ * @returns Header as object (JWT) or Map (CWT)
1484
+ */
1485
+ getHeader() {
1486
+ return this.header;
1487
+ }
1488
+ /**
1489
+ * Gets the raw payload.
1490
+ *
1491
+ * @returns Payload object
1492
+ */
1493
+ getPayload() {
1494
+ return this.payload;
1495
+ }
1496
+ /**
1497
+ * Gets the underlying StatusList instance.
1498
+ *
1499
+ * @returns StatusList instance
1500
+ */
1501
+ getStatusList() {
1502
+ return this.statusList;
1503
+ }
1504
+ };
1505
+
1506
+ export { COSE_ALGORITHMS, COSE_HEADERS, CWT_CLAIMS, CompressionError, FetchError, IndexOutOfBoundsError, InvalidBitSizeError, InvalidStatusValueError, InvalidTokenFormatError, MissingStatusListUriError, StandardStatusValues, StatusList, StatusListError, StatusListExpiredError, StatusListTokenHelper, ValidationError, calculateCapacity, compress, compressToBase64URL, createCWTStatusListPayload, createJWTStatusListPayload, decompress, decompressFromBase64URL, encodeCWTPayload, extractStatusListReference, extractStatusListReferenceCBOR, fetchStatusListToken, getBitValue, isExpired, isValidStatusListUri, packBits, parseCWTStatusList, parseCWTStatusListSigned, parseJWTStatusList, setBitValue, signCOSE, signCWTStatusList, signStatusListJWT, unpackBits, validateCWTPayload2 as validateCWTPayload, validateExpiry, validateJWTPayload2 as validateJWTPayload, validateTTLBounds, verifyCOSE, verifySignatureOnly, verifyStatusListJWT };
1507
+ //# sourceMappingURL=index.js.map
1508
+ //# sourceMappingURL=index.js.map