@peac/protocol 0.11.2 → 0.12.0-preview.1

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @peac/protocol
2
2
 
3
- PEAC protocol implementation - receipt issuance and verification
3
+ PEAC protocol implementation: receipt issuance, offline verification, and JWKS resolution.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,9 +8,74 @@ PEAC protocol implementation - receipt issuance and verification
8
8
  pnpm add @peac/protocol
9
9
  ```
10
10
 
11
- ## Documentation
11
+ ## What It Does
12
12
 
13
- See [peacprotocol.org](https://www.peacprotocol.org) for full documentation.
13
+ `@peac/protocol` is Layer 3 of the PEAC stack. It provides `issue()` for signing receipts and `verifyLocal()` for offline verification with Ed25519 public keys. No network calls needed for verification.
14
+
15
+ ## How Do I Issue a Receipt?
16
+
17
+ ```typescript
18
+ import { generateKeypair } from '@peac/crypto';
19
+ import { issue } from '@peac/protocol';
20
+
21
+ const { publicKey, privateKey } = await generateKeypair();
22
+
23
+ const { jws } = await issue({
24
+ iss: 'https://api.example.com',
25
+ aud: 'https://client.example.com',
26
+ amt: 100,
27
+ cur: 'USD',
28
+ rail: 'stripe',
29
+ reference: 'pi_abc123',
30
+ privateKey,
31
+ kid: 'key-2026-02',
32
+ });
33
+ ```
34
+
35
+ ## How Do I Verify a Receipt?
36
+
37
+ ```typescript
38
+ import { verifyLocal } from '@peac/protocol';
39
+
40
+ const result = await verifyLocal(jws, publicKey);
41
+
42
+ if (result.valid && result.variant === 'commerce') {
43
+ console.log(result.claims.iss); // issuer
44
+ console.log(result.claims.amt); // amount
45
+ console.log(result.claims.cur); // currency
46
+ } else if (!result.valid) {
47
+ console.log(result.code, result.message);
48
+ }
49
+ ```
50
+
51
+ ## How Do I Verify with JWKS Discovery?
52
+
53
+ ```typescript
54
+ import { verifyReceipt } from '@peac/protocol';
55
+
56
+ // Resolves issuer's /.well-known/peac-issuer.json -> jwks_uri -> public key
57
+ const result = await verifyReceipt(jws);
58
+
59
+ if (result.ok) {
60
+ console.log('Issuer:', result.claims.iss);
61
+ } else {
62
+ console.log(result.reason, result.details);
63
+ }
64
+ ```
65
+
66
+ ## Integrates With
67
+
68
+ - `@peac/crypto` (Layer 2): Ed25519 key generation and JWS encoding
69
+ - `@peac/kernel` (Layer 0): Error codes and wire format constants
70
+ - `@peac/schema` (Layer 1): Receipt claim validation
71
+ - `@peac/mcp-server` (Layer 5): MCP tool server using protocol functions
72
+ - `@peac/middleware-express` (Layer 3.5): Express middleware for automatic receipt issuance
73
+
74
+ ## Security
75
+
76
+ - Verification is offline and deterministic: no network calls for `verifyLocal()`
77
+ - Fail-closed: invalid or missing evidence always produces a verification failure
78
+ - JWKS resolution (when used) is SSRF-hardened with HTTPS-only, private IP denial
14
79
 
15
80
  ## License
16
81
 
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,EAMd,MAAM,cAAc,CAAC;AAMtB;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,gBAAgB,CAsFzE;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsCpF;AAiBD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAoD1F;AAkFD;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA+CtF;AAMD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAkD1D;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA2B9E"}
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,EAOd,MAAM,cAAc,CAAC;AAMtB;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,gBAAgB,CAoGzE;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsCpF;AAiBD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAoD1F;AAkFD;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA+CtF;AAMD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAkD1D;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA2B9E"}
package/dist/index.cjs CHANGED
@@ -179,6 +179,52 @@ async function issueJws(options) {
179
179
  const result = await issue(options);
180
180
  return result.jws;
181
181
  }
182
+ async function issueWire02(options) {
183
+ if (!schema.isCanonicalIss(options.iss)) {
184
+ throw new IssueError({
185
+ code: "E_ISS_NOT_CANONICAL",
186
+ category: "validation",
187
+ severity: "error",
188
+ retryable: false,
189
+ http_status: 400,
190
+ details: {
191
+ message: `iss is not in canonical form: "${options.iss}". Use https:// origin or did: identifier.`
192
+ }
193
+ });
194
+ }
195
+ const jti = options.jti ?? uuidv7.uuidv7();
196
+ const iat = Math.floor(Date.now() / 1e3);
197
+ const claims = {
198
+ peac_version: "0.2",
199
+ kind: options.kind,
200
+ type: options.type,
201
+ iss: options.iss,
202
+ iat,
203
+ jti,
204
+ ...options.sub !== void 0 && { sub: options.sub },
205
+ ...options.pillars !== void 0 && { pillars: options.pillars },
206
+ ...options.occurred_at !== void 0 && { occurred_at: options.occurred_at },
207
+ ...options.purpose_declared !== void 0 && { purpose_declared: options.purpose_declared },
208
+ ...options.policy !== void 0 && { policy: options.policy },
209
+ ...options.extensions !== void 0 && { extensions: options.extensions }
210
+ };
211
+ const parseResult = schema.Wire02ClaimsSchema.safeParse(claims);
212
+ if (!parseResult.success) {
213
+ const firstIssue = parseResult.error.issues[0];
214
+ throw new IssueError({
215
+ code: "E_INVALID_FORMAT",
216
+ category: "validation",
217
+ severity: "error",
218
+ retryable: false,
219
+ http_status: 400,
220
+ details: {
221
+ message: `Wire 0.2 claims schema validation failed: ${firstIssue?.message ?? "unknown"}`
222
+ }
223
+ });
224
+ }
225
+ const jws = await crypto.signWire02(claims, options.privateKey, options.kid);
226
+ return { jws };
227
+ }
182
228
  function parseIssuerConfig(json) {
183
229
  let config;
184
230
  if (typeof json === "string") {
@@ -238,6 +284,17 @@ function parseIssuerConfig(json) {
238
284
  throw new Error("payment_rails must be an array");
239
285
  }
240
286
  }
287
+ let revokedKeys;
288
+ if (obj.revoked_keys !== void 0) {
289
+ if (!Array.isArray(obj.revoked_keys)) {
290
+ throw new Error("revoked_keys must be an array");
291
+ }
292
+ const result = schema.validateRevokedKeys(obj.revoked_keys);
293
+ if (!result.ok) {
294
+ throw new Error(`Invalid revoked_keys: ${result.error}`);
295
+ }
296
+ revokedKeys = result.value;
297
+ }
241
298
  return {
242
299
  version: obj.version,
243
300
  issuer: obj.issuer,
@@ -246,7 +303,8 @@ function parseIssuerConfig(json) {
246
303
  receipt_versions: obj.receipt_versions,
247
304
  algorithms: obj.algorithms,
248
305
  payment_rails: obj.payment_rails,
249
- security_contact: obj.security_contact
306
+ security_contact: obj.security_contact,
307
+ revoked_keys: revokedKeys
250
308
  };
251
309
  }
252
310
  async function fetchIssuerConfig(issuerUrl) {
@@ -933,6 +991,53 @@ async function fetchPointerSafe(pointerUrl, options) {
933
991
  var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
934
992
  var DEFAULT_MAX_CACHE_ENTRIES = 1e3;
935
993
  var jwksCache = /* @__PURE__ */ new Map();
994
+ var KID_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
995
+ var MAX_KID_THUMBPRINT_ENTRIES = 1e4;
996
+ var kidThumbprints = /* @__PURE__ */ new Map();
997
+ function pruneExpiredKidEntries(now) {
998
+ for (const [key, entry] of kidThumbprints) {
999
+ if (now - entry.firstSeen >= KID_RETENTION_MS) {
1000
+ kidThumbprints.delete(key);
1001
+ }
1002
+ }
1003
+ }
1004
+ function evictOldestKidEntries() {
1005
+ if (kidThumbprints.size <= MAX_KID_THUMBPRINT_ENTRIES) return;
1006
+ const entries = Array.from(kidThumbprints.entries()).sort(
1007
+ (a, b) => a[1].firstSeen - b[1].firstSeen
1008
+ );
1009
+ const toRemove = kidThumbprints.size - MAX_KID_THUMBPRINT_ENTRIES;
1010
+ for (let i = 0; i < toRemove; i++) {
1011
+ kidThumbprints.delete(entries[i][0]);
1012
+ }
1013
+ }
1014
+ function checkKidReuse(issuer, jwks, now) {
1015
+ pruneExpiredKidEntries(now);
1016
+ for (const key of jwks.keys) {
1017
+ if (!key.kid || !key.x) continue;
1018
+ const mapKey = `${issuer}|${key.kid}`;
1019
+ const existing = kidThumbprints.get(mapKey);
1020
+ if (existing) {
1021
+ if (now - existing.firstSeen < KID_RETENTION_MS) {
1022
+ if (existing.thumbprint !== key.x) {
1023
+ return `Kid reuse detected: kid=${key.kid} for issuer ${issuer} maps to different key material`;
1024
+ }
1025
+ } else {
1026
+ kidThumbprints.set(mapKey, { thumbprint: key.x, firstSeen: now });
1027
+ }
1028
+ } else {
1029
+ kidThumbprints.set(mapKey, { thumbprint: key.x, firstSeen: now });
1030
+ }
1031
+ }
1032
+ evictOldestKidEntries();
1033
+ return null;
1034
+ }
1035
+ function clearKidThumbprints() {
1036
+ kidThumbprints.clear();
1037
+ }
1038
+ function getKidThumbprintSize() {
1039
+ return kidThumbprints.size;
1040
+ }
936
1041
  function cacheGet(key, now) {
937
1042
  const entry = jwksCache.get(key);
938
1043
  if (!entry) return void 0;
@@ -1021,7 +1126,7 @@ async function resolveJWKS(issuerUrl, options) {
1021
1126
  if (!noCache) {
1022
1127
  const cached = cacheGet(normalizedIssuer, now);
1023
1128
  if (cached) {
1024
- return { ok: true, jwks: cached.jwks, fromCache: true };
1129
+ return { ok: true, jwks: cached.jwks, fromCache: true, revokedKeys: cached.revokedKeys };
1025
1130
  }
1026
1131
  }
1027
1132
  if (!normalizedIssuer.startsWith("https://")) {
@@ -1116,14 +1221,28 @@ async function resolveJWKS(issuerUrl, options) {
1116
1221
  message: `JWKS has too many keys: ${jwks.keys.length} > ${kernel.VERIFIER_LIMITS.maxJwksKeys}`
1117
1222
  };
1118
1223
  }
1224
+ const kidReuseError = checkKidReuse(normalizedIssuer, jwks, now);
1225
+ if (kidReuseError) {
1226
+ return {
1227
+ ok: false,
1228
+ code: "E_KID_REUSE_DETECTED",
1229
+ message: kidReuseError
1230
+ };
1231
+ }
1232
+ const revokedKeys = issuerConfig.revoked_keys?.map((entry) => ({
1233
+ kid: entry.kid,
1234
+ revoked_at: entry.revoked_at,
1235
+ reason: entry.reason
1236
+ }));
1119
1237
  if (!noCache) {
1120
- cacheSet(normalizedIssuer, { jwks, expiresAt: now + cacheTtlMs }, maxCacheEntries);
1238
+ cacheSet(normalizedIssuer, { jwks, expiresAt: now + cacheTtlMs, revokedKeys }, maxCacheEntries);
1121
1239
  }
1122
1240
  return {
1123
1241
  ok: true,
1124
1242
  jwks,
1125
1243
  fromCache: false,
1126
- rawBytes: jwksResult.rawBytes
1244
+ rawBytes: jwksResult.rawBytes,
1245
+ revokedKeys
1127
1246
  };
1128
1247
  }
1129
1248
 
@@ -1184,6 +1303,25 @@ async function verifyReceipt(optionsOrJws) {
1184
1303
  if (!jwksResult.fromCache) {
1185
1304
  jwksFetchTime = performance.now() - jwksFetchStart;
1186
1305
  }
1306
+ if (jwksResult.revokedKeys) {
1307
+ const revokedEntry = jwksResult.revokedKeys.find((rk) => rk.kid === header.kid);
1308
+ if (revokedEntry) {
1309
+ const durationMs = performance.now() - startTime;
1310
+ fireTelemetryHook(telemetry?.onReceiptVerified, {
1311
+ receiptHash: hashReceipt(receiptJws),
1312
+ valid: false,
1313
+ reasonCode: "key_revoked",
1314
+ issuer: payload.iss,
1315
+ kid: header.kid,
1316
+ durationMs
1317
+ });
1318
+ return {
1319
+ ok: false,
1320
+ reason: "key_revoked",
1321
+ details: `Key kid=${header.kid} was revoked at ${revokedEntry.revoked_at}${revokedEntry.reason ? ` (reason: ${revokedEntry.reason})` : ""}`
1322
+ };
1323
+ }
1324
+ }
1187
1325
  const jwk = jwksResult.jwks.keys.find((k) => k.kid === header.kid);
1188
1326
  if (!jwk) {
1189
1327
  const durationMs = performance.now() - startTime;
@@ -1261,6 +1399,13 @@ var FORMAT_ERROR_CODES = /* @__PURE__ */ new Set([
1261
1399
  "CRYPTO_INVALID_ALG",
1262
1400
  "CRYPTO_INVALID_KEY_LENGTH"
1263
1401
  ]);
1402
+ var JOSE_CODE_MAP = {
1403
+ CRYPTO_JWS_EMBEDDED_KEY: "E_JWS_EMBEDDED_KEY",
1404
+ CRYPTO_JWS_CRIT_REJECTED: "E_JWS_CRIT_REJECTED",
1405
+ CRYPTO_JWS_MISSING_KID: "E_JWS_MISSING_KID",
1406
+ CRYPTO_JWS_B64_REJECTED: "E_JWS_B64_REJECTED",
1407
+ CRYPTO_JWS_ZIP_REJECTED: "E_JWS_ZIP_REJECTED"
1408
+ };
1264
1409
  var MAX_PARSE_ISSUES = 25;
1265
1410
  function sanitizeParseIssues(issues) {
1266
1411
  if (!Array.isArray(issues)) return void 0;
@@ -1270,7 +1415,16 @@ function sanitizeParseIssues(issues) {
1270
1415
  }));
1271
1416
  }
1272
1417
  async function verifyLocal(jws, publicKey, options = {}) {
1273
- const { issuer, audience, subjectUri, rid, requireExp = false, maxClockSkew = 300 } = options;
1418
+ const {
1419
+ issuer,
1420
+ audience,
1421
+ subjectUri,
1422
+ rid,
1423
+ requireExp = false,
1424
+ maxClockSkew = 300,
1425
+ strictness = "strict",
1426
+ policyDigest
1427
+ } = options;
1274
1428
  const now = options.now ?? Math.floor(Date.now() / 1e3);
1275
1429
  try {
1276
1430
  const result = await crypto.verify(jws, publicKey);
@@ -1281,6 +1435,20 @@ async function verifyLocal(jws, publicKey, options = {}) {
1281
1435
  message: "Ed25519 signature verification failed"
1282
1436
  };
1283
1437
  }
1438
+ const accumulatedWarnings = [];
1439
+ if (result.header.typ === void 0) {
1440
+ if (strictness === "strict") {
1441
+ return {
1442
+ valid: false,
1443
+ code: "E_INVALID_FORMAT",
1444
+ message: "Missing JWS typ header: strict mode requires typ to be present"
1445
+ };
1446
+ }
1447
+ accumulatedWarnings.push({
1448
+ code: schema.WARNING_TYP_MISSING,
1449
+ message: "JWS typ header is absent; accepted in interop mode"
1450
+ });
1451
+ }
1284
1452
  const constraintResult = schema.validateKernelConstraints(result.payload);
1285
1453
  if (!constraintResult.valid) {
1286
1454
  const v = constraintResult.violations[0];
@@ -1299,46 +1467,136 @@ async function verifyLocal(jws, publicKey, options = {}) {
1299
1467
  details: { parse_code: pr.error.code, issues: sanitizeParseIssues(pr.error.issues) }
1300
1468
  };
1301
1469
  }
1302
- if (issuer !== void 0 && pr.claims.iss !== issuer) {
1470
+ if (pr.wireVersion === "0.2") {
1471
+ accumulatedWarnings.push(...pr.warnings);
1472
+ }
1473
+ if (pr.wireVersion === "0.2") {
1474
+ const claims = pr.claims;
1475
+ if (issuer !== void 0 && claims.iss !== issuer) {
1476
+ return {
1477
+ valid: false,
1478
+ code: "E_INVALID_ISSUER",
1479
+ message: `Issuer mismatch: expected "${issuer}", got "${claims.iss}"`
1480
+ };
1481
+ }
1482
+ if (subjectUri !== void 0 && claims.sub !== subjectUri) {
1483
+ return {
1484
+ valid: false,
1485
+ code: "E_INVALID_SUBJECT",
1486
+ message: `Subject mismatch: expected "${subjectUri}", got "${claims.sub ?? "undefined"}"`
1487
+ };
1488
+ }
1489
+ if (claims.iat > now + maxClockSkew) {
1490
+ return {
1491
+ valid: false,
1492
+ code: "E_NOT_YET_VALID",
1493
+ message: `Receipt not yet valid: issued at ${new Date(claims.iat * 1e3).toISOString()}, now is ${new Date(now * 1e3).toISOString()}`
1494
+ };
1495
+ }
1496
+ if (claims.kind === "evidence") {
1497
+ const skewResult = schema.checkOccurredAtSkew(claims.occurred_at, claims.iat, now, maxClockSkew);
1498
+ if (skewResult === "future_error") {
1499
+ return {
1500
+ valid: false,
1501
+ code: "E_OCCURRED_AT_FUTURE",
1502
+ message: `occurred_at is in the future beyond tolerance (${maxClockSkew}s)`
1503
+ };
1504
+ }
1505
+ if (skewResult !== null) {
1506
+ accumulatedWarnings.push(skewResult);
1507
+ }
1508
+ }
1509
+ if (!schema.REGISTERED_RECEIPT_TYPES.has(claims.type)) {
1510
+ accumulatedWarnings.push({
1511
+ code: schema.WARNING_TYPE_UNREGISTERED,
1512
+ message: "Receipt type is not in the recommended type registry",
1513
+ pointer: "/type"
1514
+ });
1515
+ }
1516
+ if (claims.extensions !== void 0) {
1517
+ for (const key of Object.keys(claims.extensions)) {
1518
+ if (!schema.REGISTERED_EXTENSION_GROUP_KEYS.has(key) && schema.isValidExtensionKey(key)) {
1519
+ const escapedKey = key.replace(/~/g, "~0").replace(/\//g, "~1");
1520
+ accumulatedWarnings.push({
1521
+ code: schema.WARNING_UNKNOWN_EXTENSION,
1522
+ message: "Unknown extension key preserved without schema validation",
1523
+ pointer: `/extensions/${escapedKey}`
1524
+ });
1525
+ }
1526
+ }
1527
+ }
1528
+ if (policyDigest !== void 0 && !kernel.HASH.pattern.test(policyDigest)) {
1529
+ return {
1530
+ valid: false,
1531
+ code: "E_INVALID_FORMAT",
1532
+ message: "policyDigest option must be in sha256:<64 lowercase hex> format"
1533
+ };
1534
+ }
1535
+ const receiptPolicyDigest = claims.policy?.digest;
1536
+ const bindingStatus = receiptPolicyDigest === void 0 || policyDigest === void 0 ? "unavailable" : schema.verifyPolicyBinding(receiptPolicyDigest, policyDigest);
1537
+ if (bindingStatus === "failed") {
1538
+ return {
1539
+ valid: false,
1540
+ code: "E_POLICY_BINDING_FAILED",
1541
+ message: "Policy binding check failed: receipt policy digest does not match local policy",
1542
+ details: {
1543
+ receipt_policy_digest: receiptPolicyDigest,
1544
+ local_policy_digest: policyDigest,
1545
+ ...claims.policy?.uri !== void 0 && { policy_uri: claims.policy.uri }
1546
+ }
1547
+ };
1548
+ }
1549
+ return {
1550
+ valid: true,
1551
+ variant: "wire-02",
1552
+ claims,
1553
+ kid: result.header.kid,
1554
+ wireVersion: "0.2",
1555
+ warnings: schema.sortWarnings(accumulatedWarnings),
1556
+ policy_binding: bindingStatus
1557
+ };
1558
+ }
1559
+ const w01 = pr.claims;
1560
+ if (issuer !== void 0 && w01.iss !== issuer) {
1303
1561
  return {
1304
1562
  valid: false,
1305
1563
  code: "E_INVALID_ISSUER",
1306
- message: `Issuer mismatch: expected "${issuer}", got "${pr.claims.iss}"`
1564
+ message: `Issuer mismatch: expected "${issuer}", got "${w01.iss}"`
1307
1565
  };
1308
1566
  }
1309
- if (audience !== void 0 && pr.claims.aud !== audience) {
1567
+ if (audience !== void 0 && w01.aud !== audience) {
1310
1568
  return {
1311
1569
  valid: false,
1312
1570
  code: "E_INVALID_AUDIENCE",
1313
- message: `Audience mismatch: expected "${audience}", got "${pr.claims.aud}"`
1571
+ message: `Audience mismatch: expected "${audience}", got "${w01.aud}"`
1314
1572
  };
1315
1573
  }
1316
- if (rid !== void 0 && pr.claims.rid !== rid) {
1574
+ if (rid !== void 0 && w01.rid !== rid) {
1317
1575
  return {
1318
1576
  valid: false,
1319
1577
  code: "E_INVALID_RECEIPT_ID",
1320
- message: `Receipt ID mismatch: expected "${rid}", got "${pr.claims.rid}"`
1578
+ message: `Receipt ID mismatch: expected "${rid}", got "${w01.rid}"`
1321
1579
  };
1322
1580
  }
1323
- if (requireExp && pr.claims.exp === void 0) {
1581
+ if (requireExp && w01.exp === void 0) {
1324
1582
  return {
1325
1583
  valid: false,
1326
1584
  code: "E_MISSING_EXP",
1327
1585
  message: "Receipt missing required exp claim"
1328
1586
  };
1329
1587
  }
1330
- if (pr.claims.iat > now + maxClockSkew) {
1588
+ if (w01.iat > now + maxClockSkew) {
1331
1589
  return {
1332
1590
  valid: false,
1333
1591
  code: "E_NOT_YET_VALID",
1334
- message: `Receipt not yet valid: issued at ${new Date(pr.claims.iat * 1e3).toISOString()}, now is ${new Date(now * 1e3).toISOString()}`
1592
+ message: `Receipt not yet valid: issued at ${new Date(w01.iat * 1e3).toISOString()}, now is ${new Date(now * 1e3).toISOString()}`
1335
1593
  };
1336
1594
  }
1337
- if (pr.claims.exp !== void 0 && pr.claims.exp < now - maxClockSkew) {
1595
+ if (w01.exp !== void 0 && w01.exp < now - maxClockSkew) {
1338
1596
  return {
1339
1597
  valid: false,
1340
1598
  code: "E_EXPIRED",
1341
- message: `Receipt expired at ${new Date(pr.claims.exp * 1e3).toISOString()}`
1599
+ message: `Receipt expired at ${new Date(w01.exp * 1e3).toISOString()}`
1342
1600
  };
1343
1601
  }
1344
1602
  if (pr.variant === "commerce") {
@@ -1355,6 +1613,8 @@ async function verifyLocal(jws, publicKey, options = {}) {
1355
1613
  variant: "commerce",
1356
1614
  claims,
1357
1615
  kid: result.header.kid,
1616
+ wireVersion: "0.1",
1617
+ warnings: [],
1358
1618
  policy_binding: "unavailable"
1359
1619
  };
1360
1620
  } else {
@@ -1371,11 +1631,20 @@ async function verifyLocal(jws, publicKey, options = {}) {
1371
1631
  variant: "attestation",
1372
1632
  claims,
1373
1633
  kid: result.header.kid,
1634
+ wireVersion: "0.1",
1635
+ warnings: [],
1374
1636
  policy_binding: "unavailable"
1375
1637
  };
1376
1638
  }
1377
1639
  } catch (err) {
1378
1640
  if (isCryptoError(err)) {
1641
+ if (Object.prototype.hasOwnProperty.call(JOSE_CODE_MAP, err.code)) {
1642
+ return {
1643
+ valid: false,
1644
+ code: JOSE_CODE_MAP[err.code],
1645
+ message: err.message
1646
+ };
1647
+ }
1379
1648
  if (FORMAT_ERROR_CODES.has(err.code)) {
1380
1649
  return {
1381
1650
  valid: false,
@@ -1390,6 +1659,13 @@ async function verifyLocal(jws, publicKey, options = {}) {
1390
1659
  message: err.message
1391
1660
  };
1392
1661
  }
1662
+ if (err.code === "CRYPTO_WIRE_VERSION_MISMATCH") {
1663
+ return {
1664
+ valid: false,
1665
+ code: "E_WIRE_VERSION_MISMATCH",
1666
+ message: err.message
1667
+ };
1668
+ }
1393
1669
  }
1394
1670
  if (err !== null && typeof err === "object" && "name" in err && err.name === "SyntaxError") {
1395
1671
  const syntaxMessage = "message" in err && typeof err.message === "string" ? err.message : "Invalid JSON";
@@ -1413,6 +1689,9 @@ function isCommerceResult(r) {
1413
1689
  function isAttestationResult(r) {
1414
1690
  return r.valid === true && r.variant === "attestation";
1415
1691
  }
1692
+ function isWire02Result(r) {
1693
+ return r.valid === true && r.variant === "wire-02";
1694
+ }
1416
1695
  function setReceiptHeader(headers, receiptJws) {
1417
1696
  headers.set(schema.PEAC_RECEIPT_HEADER, receiptJws);
1418
1697
  }
@@ -1454,6 +1733,16 @@ function setVaryPurposeHeader(headers) {
1454
1733
  headers.set("Vary", schema.PEAC_PURPOSE_HEADER);
1455
1734
  }
1456
1735
  }
1736
+ async function computePolicyDigestJcs(policy) {
1737
+ const hex = await crypto.jcsHash(policy);
1738
+ return `${kernel.HASH.prefix}${hex}`;
1739
+ }
1740
+ function checkPolicyBinding(receiptDigest, localDigest) {
1741
+ if (receiptDigest === void 0 || localDigest === void 0) {
1742
+ return "unavailable";
1743
+ }
1744
+ return schema.verifyPolicyBinding(receiptDigest, localDigest);
1745
+ }
1457
1746
  var DEFAULT_VERIFIER_LIMITS = {
1458
1747
  max_receipt_bytes: kernel.VERIFIER_LIMITS.maxReceiptBytes,
1459
1748
  max_jwks_bytes: kernel.VERIFIER_LIMITS.maxJwksBytes,
@@ -2769,7 +3058,10 @@ exports.NON_DETERMINISTIC_ARTIFACT_KEYS = NON_DETERMINISTIC_ARTIFACT_KEYS;
2769
3058
  exports.VerificationReportBuilder = VerificationReportBuilder;
2770
3059
  exports.buildFailureReport = buildFailureReport;
2771
3060
  exports.buildSuccessReport = buildSuccessReport;
3061
+ exports.checkPolicyBinding = checkPolicyBinding;
2772
3062
  exports.clearJWKSCache = clearJWKSCache;
3063
+ exports.clearKidThumbprints = clearKidThumbprints;
3064
+ exports.computePolicyDigestJcs = computePolicyDigestJcs;
2773
3065
  exports.computeReceiptDigest = computeReceiptDigest;
2774
3066
  exports.createDefaultPolicy = createDefaultPolicy;
2775
3067
  exports.createDigest = createDigest;
@@ -2782,14 +3074,17 @@ exports.fetchPointerSafe = fetchPointerSafe;
2782
3074
  exports.fetchPointerWithDigest = fetchPointerWithDigest;
2783
3075
  exports.fetchPolicyManifest = fetchPolicyManifest;
2784
3076
  exports.getJWKSCacheSize = getJWKSCacheSize;
3077
+ exports.getKidThumbprintSize = getKidThumbprintSize;
2785
3078
  exports.getPurposeHeader = getPurposeHeader;
2786
3079
  exports.getReceiptHeader = getReceiptHeader;
2787
3080
  exports.getSSRFCapabilities = getSSRFCapabilities;
2788
3081
  exports.isAttestationResult = isAttestationResult;
2789
3082
  exports.isBlockedIP = isBlockedIP;
2790
3083
  exports.isCommerceResult = isCommerceResult;
3084
+ exports.isWire02Result = isWire02Result;
2791
3085
  exports.issue = issue;
2792
3086
  exports.issueJws = issueJws;
3087
+ exports.issueWire02 = issueWire02;
2793
3088
  exports.parseBodyProfile = parseBodyProfile;
2794
3089
  exports.parseDiscovery = parseDiscovery;
2795
3090
  exports.parseHeaderProfile = parseHeaderProfile;