@peac/protocol 0.11.3 → 0.12.0-preview.2

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/issue.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Receipt issuance
3
3
  * Validates input, generates UUIDv7 rid, and signs with Ed25519
4
4
  */
5
- import type { JsonValue } from '@peac/kernel';
5
+ import type { JsonValue, EvidencePillar, PolicyBlock } from '@peac/kernel';
6
6
  import { PEACReceiptClaims, SubjectProfileSnapshot, type PEACError, type PurposeToken, type CanonicalPurpose, type PurposeReason, type WorkflowContext } from '@peac/schema';
7
7
  import { type TelemetryHook } from './telemetry.js';
8
8
  /**
@@ -97,7 +97,10 @@ export declare class IssueError extends Error {
97
97
  constructor(peacError: PEACError);
98
98
  }
99
99
  /**
100
- * Issue a PEAC receipt
100
+ * Issue a Wire 0.1 PEAC receipt.
101
+ *
102
+ * @deprecated Use {@link issueWire02} for Wire 0.2 receipts. Wire 0.1 issuance is deprecated
103
+ * and will be removed in a future major version.
101
104
  *
102
105
  * @param options - Receipt options
103
106
  * @returns Issue result with JWS and optional subject_snapshot
@@ -114,4 +117,63 @@ export declare function issue(options: IssueOptions): Promise<IssueResult>;
114
117
  * @returns JWS compact serialization
115
118
  */
116
119
  export declare function issueJws(options: IssueOptions): Promise<string>;
120
+ /**
121
+ * Options for issuing a Wire 0.2 receipt
122
+ */
123
+ export interface IssueWire02Options {
124
+ /**
125
+ * Canonical issuer.
126
+ * Accepted: https:// ASCII origin or did: identifier.
127
+ * Non-canonical values produce an IssueError.
128
+ */
129
+ iss: string;
130
+ /** Structural kind: 'evidence' or 'challenge' */
131
+ kind: 'evidence' | 'challenge';
132
+ /**
133
+ * Open semantic type.
134
+ * Accepted: reverse-DNS notation (e.g., 'org.example/flow') or absolute URI.
135
+ */
136
+ type: string;
137
+ /** Ed25519 private key (32 bytes) */
138
+ privateKey: Uint8Array;
139
+ /** Key ID (max 256 chars per JOSE hardening rules) */
140
+ kid: string;
141
+ /**
142
+ * Unique receipt identifier.
143
+ * Generated via uuidv7 if not provided.
144
+ */
145
+ jti?: string;
146
+ /** Subject identifier (max 2048 chars, optional) */
147
+ sub?: string;
148
+ /**
149
+ * Evidence pillars (sorted ascending, closed 10-value taxonomy).
150
+ * Validated against Wire02ClaimsSchema before signing.
151
+ */
152
+ pillars?: EvidencePillar[];
153
+ /**
154
+ * ISO 8601 / RFC 3339 timestamp when the interaction occurred.
155
+ * Evidence kind only; rejected on challenge kind with E_OCCURRED_AT_ON_CHALLENGE.
156
+ */
157
+ occurred_at?: string;
158
+ /** Declared purpose string (max 256 chars, optional) */
159
+ purpose_declared?: string;
160
+ /**
161
+ * Policy binding block (DD-151).
162
+ * digest must be 'sha256:<64 lowercase hex>' format (use computePolicyDigestJcs from @peac/protocol).
163
+ */
164
+ policy?: PolicyBlock;
165
+ /** Extension groups (open; caller-provided, not validated here) */
166
+ extensions?: Record<string, unknown>;
167
+ }
168
+ /**
169
+ * Issue a Wire 0.2 receipt
170
+ *
171
+ * Validates the iss canonical form and Wire02ClaimsSchema before signing.
172
+ * Always sets typ to 'interaction-record+jwt' (WIRE_02_JWS_TYP).
173
+ *
174
+ * @param options - Wire 0.2 receipt options
175
+ * @returns Issue result with JWS
176
+ * @throws IssueError if iss is not canonical or schema validation fails
177
+ */
178
+ export declare function issueWire02(options: IssueWire02Options): Promise<IssueResult>;
117
179
  //# sourceMappingURL=issue.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"issue.d.ts","sourceRoot":"","sources":["../src/issue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EACL,iBAAiB,EAEjB,sBAAsB,EAOtB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAKlB,KAAK,eAAe,EAIrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAkC,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpF;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;IAEZ,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC;IAEZ,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IAEZ,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC;IAEZ,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IAEb,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAElB,4FAA4F;IAC5F,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAEtB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,uCAAuC;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB,iCAAiC;IACjC,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnC,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,4BAA4B;IAC5B,GAAG,CAAC,EAAE,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAE/B,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,iEAAiE;IACjE,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAE1C;;;;;OAKG;IACH,OAAO,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC;IAExC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAEpC;;;;OAIG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC;IAE/B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,eAAe,CAAC;IAEnC,qCAAqC;IACrC,UAAU,EAAE,UAAU,CAAC;IAEvB,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IAEZ,iDAAiD;IACjD,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IAEZ,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;CAC3C;AAED;;;;GAIG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACnC,+BAA+B;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;gBAElB,SAAS,EAAE,SAAS;CAMjC;AAED;;;;;;GAMG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAmLvE;AAED;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAGrE"}
1
+ {"version":3,"file":"issue.d.ts","sourceRoot":"","sources":["../src/issue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Policy binding utilities (Layer 3, DD-49, DD-151)
3
+ *
4
+ * JCS (RFC 8785) canonicalization + SHA-256 for policy digest computation,
5
+ * and the 3-state binding check that combines receipt and local digests.
6
+ *
7
+ * The JCS + hash computation lives in Layer 3 (@peac/protocol) rather than
8
+ * Layer 1 (@peac/schema) to avoid introducing crypto dependencies into the
9
+ * schema package (DD-141: @peac/schema is validation-only).
10
+ *
11
+ * The pure string comparison (verifyPolicyBinding) lives in Layer 1
12
+ * (@peac/schema) so that consumers who only need to check pre-computed
13
+ * digests do not need to pull in protocol-level dependencies.
14
+ */
15
+ import type { JsonValue } from '@peac/kernel';
16
+ import type { PolicyBindingStatus } from './verifier-types.js';
17
+ /**
18
+ * Compute the JCS+SHA-256 digest of a policy object.
19
+ *
20
+ * Canonicalizes the policy value via RFC 8785 (JSON Canonicalization Scheme),
21
+ * computes SHA-256 over the resulting UTF-8 bytes, and returns the result in
22
+ * the PEAC self-describing hash format: 'sha256:<64 lowercase hex>'.
23
+ *
24
+ * This is the normative digest format for the policy.digest field in Wire 0.2
25
+ * receipts (DD-151). The format is stable and identical across implementations.
26
+ *
27
+ * Callers MUST pass the same JSON structure that was embedded in the receipt's
28
+ * policy block when issuing. Key order is irrelevant; JCS normalizes it.
29
+ *
30
+ * @param policy - Policy value (any JSON-serializable value)
31
+ * @returns Digest string in 'sha256:<64 lowercase hex>' format
32
+ * @throws Error if the value cannot be canonicalized (e.g., contains functions
33
+ * or non-finite numbers)
34
+ */
35
+ export declare function computePolicyDigestJcs(policy: JsonValue): Promise<string>;
36
+ /**
37
+ * Compute the 3-state policy binding result.
38
+ *
39
+ * Three-state semantics (DD-151):
40
+ * - 'unavailable': either digest is absent (receipt has no policy block, or
41
+ * caller did not provide a local digest). No binding check performed.
42
+ * - 'verified': both digests present and match exactly.
43
+ * - 'failed': both digests present but do not match.
44
+ *
45
+ * When the result is 'failed', verifyLocal() returns E_POLICY_BINDING_FAILED
46
+ * as a hard verification error.
47
+ *
48
+ * @param receiptDigest - policy.digest from the receipt claims; undefined if
49
+ * the receipt contains no policy block
50
+ * @param localDigest - digest computed from the caller's local policy bytes via
51
+ * computePolicyDigestJcs(); undefined if the caller has no policy to check
52
+ * @returns Three-state PolicyBindingStatus
53
+ */
54
+ export declare function checkPolicyBinding(receiptDigest: string | undefined, localDigest: string | undefined): PolicyBindingStatus;
55
+ //# sourceMappingURL=policy-binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-binding.d.ts","sourceRoot":"","sources":["../src/policy-binding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG/D;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAG/E;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,mBAAmB,CAKrB"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Wire 0.1 receipt verification (internal-only)
3
+ *
4
+ * Extracted from verify-local.ts for Wire 0.1 isolation.
5
+ * This function is NOT exported from @peac/protocol barrel (src/index.ts).
6
+ * It exists for internal test migration and programmatic migration tooling only.
7
+ *
8
+ * For new code, use verifyLocal() which is Wire 0.2 only.
9
+ */
10
+ import type { VerificationWarning } from '@peac/kernel';
11
+ import { type ReceiptClaimsType, type AttestationReceiptClaims } from '@peac/schema';
12
+ import type { PolicyBindingStatus } from './verifier-types';
13
+ import type { VerifyLocalFailure, VerifyLocalOptions } from './verify-local';
14
+ /**
15
+ * Result of successful Wire 0.1 local verification
16
+ */
17
+ export type VerifyLocalWire01Success = {
18
+ valid: true;
19
+ variant: 'commerce';
20
+ claims: ReceiptClaimsType;
21
+ kid: string;
22
+ wireVersion: '0.1';
23
+ warnings: VerificationWarning[];
24
+ policy_binding: PolicyBindingStatus;
25
+ } | {
26
+ valid: true;
27
+ variant: 'attestation';
28
+ claims: AttestationReceiptClaims;
29
+ kid: string;
30
+ wireVersion: '0.1';
31
+ warnings: VerificationWarning[];
32
+ policy_binding: PolicyBindingStatus;
33
+ };
34
+ /**
35
+ * Union type for Wire 0.1 local verification result
36
+ */
37
+ export type VerifyLocalWire01Result = VerifyLocalWire01Success | VerifyLocalFailure;
38
+ /**
39
+ * Verify a Wire 0.1 PEAC receipt locally with a known public key.
40
+ *
41
+ * Internal-only: NOT barrel-exported from @peac/protocol.
42
+ * For new code, use verifyLocal() (Wire 0.2 only).
43
+ */
44
+ export declare function verifyLocalWire01(jws: string, publicKey: Uint8Array, options?: VerifyLocalOptions): Promise<VerifyLocalWire01Result>;
45
+ //# sourceMappingURL=verify-local-wire01.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-local-wire01.d.ts","sourceRoot":"","sources":["../src/verify-local-wire01.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAA0B,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC9B,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAwB,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEnG;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAChC;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,OAAO,EAAE,UAAU,CAAC;IACpB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,KAAK,CAAC;IACnB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,cAAc,EAAE,mBAAmB,CAAC;CACrC,GACD;IACE,KAAK,EAAE,IAAI,CAAC;IACZ,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,wBAAwB,CAAC;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,KAAK,CAAC;IACnB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,cAAc,EAAE,mBAAmB,CAAC;CACrC,CAAC;AAEN;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,wBAAwB,GAAG,kBAAkB,CAAC;AAoDpF;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,UAAU,EACrB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,uBAAuB,CAAC,CAqNlC"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var crypto = require('@peac/crypto');
4
+ var kernel = require('@peac/kernel');
4
5
  var schema = require('@peac/schema');
5
6
 
6
7
  // src/verify-local.ts
@@ -13,6 +14,13 @@ var FORMAT_ERROR_CODES = /* @__PURE__ */ new Set([
13
14
  "CRYPTO_INVALID_ALG",
14
15
  "CRYPTO_INVALID_KEY_LENGTH"
15
16
  ]);
17
+ var JOSE_CODE_MAP = {
18
+ CRYPTO_JWS_EMBEDDED_KEY: "E_JWS_EMBEDDED_KEY",
19
+ CRYPTO_JWS_CRIT_REJECTED: "E_JWS_CRIT_REJECTED",
20
+ CRYPTO_JWS_MISSING_KID: "E_JWS_MISSING_KID",
21
+ CRYPTO_JWS_B64_REJECTED: "E_JWS_B64_REJECTED",
22
+ CRYPTO_JWS_ZIP_REJECTED: "E_JWS_ZIP_REJECTED"
23
+ };
16
24
  var MAX_PARSE_ISSUES = 25;
17
25
  function sanitizeParseIssues(issues) {
18
26
  if (!Array.isArray(issues)) return void 0;
@@ -22,7 +30,7 @@ function sanitizeParseIssues(issues) {
22
30
  }));
23
31
  }
24
32
  async function verifyLocal(jws, publicKey, options = {}) {
25
- const { issuer, audience, subjectUri, rid, requireExp = false, maxClockSkew = 300 } = options;
33
+ const { issuer, subjectUri, maxClockSkew = 300, strictness = "strict", policyDigest } = options;
26
34
  const now = options.now ?? Math.floor(Date.now() / 1e3);
27
35
  try {
28
36
  const result = await crypto.verify(jws, publicKey);
@@ -33,6 +41,20 @@ async function verifyLocal(jws, publicKey, options = {}) {
33
41
  message: "Ed25519 signature verification failed"
34
42
  };
35
43
  }
44
+ const accumulatedWarnings = [];
45
+ if (result.header.typ === void 0) {
46
+ if (strictness === "strict") {
47
+ return {
48
+ valid: false,
49
+ code: "E_INVALID_FORMAT",
50
+ message: "Missing JWS typ header: strict mode requires typ to be present"
51
+ };
52
+ }
53
+ accumulatedWarnings.push({
54
+ code: schema.WARNING_TYP_MISSING,
55
+ message: "JWS typ header is absent; accepted in interop mode"
56
+ });
57
+ }
36
58
  const constraintResult = schema.validateKernelConstraints(result.payload);
37
59
  if (!constraintResult.valid) {
38
60
  const v = constraintResult.violations[0];
@@ -51,66 +73,18 @@ async function verifyLocal(jws, publicKey, options = {}) {
51
73
  details: { parse_code: pr.error.code, issues: sanitizeParseIssues(pr.error.issues) }
52
74
  };
53
75
  }
54
- if (issuer !== void 0 && pr.claims.iss !== issuer) {
55
- return {
56
- valid: false,
57
- code: "E_INVALID_ISSUER",
58
- message: `Issuer mismatch: expected "${issuer}", got "${pr.claims.iss}"`
59
- };
60
- }
61
- if (audience !== void 0 && pr.claims.aud !== audience) {
62
- return {
63
- valid: false,
64
- code: "E_INVALID_AUDIENCE",
65
- message: `Audience mismatch: expected "${audience}", got "${pr.claims.aud}"`
66
- };
67
- }
68
- if (rid !== void 0 && pr.claims.rid !== rid) {
69
- return {
70
- valid: false,
71
- code: "E_INVALID_RECEIPT_ID",
72
- message: `Receipt ID mismatch: expected "${rid}", got "${pr.claims.rid}"`
73
- };
76
+ if (pr.wireVersion === "0.2") {
77
+ accumulatedWarnings.push(...pr.warnings);
74
78
  }
75
- if (requireExp && pr.claims.exp === void 0) {
76
- return {
77
- valid: false,
78
- code: "E_MISSING_EXP",
79
- message: "Receipt missing required exp claim"
80
- };
81
- }
82
- if (pr.claims.iat > now + maxClockSkew) {
83
- return {
84
- valid: false,
85
- code: "E_NOT_YET_VALID",
86
- message: `Receipt not yet valid: issued at ${new Date(pr.claims.iat * 1e3).toISOString()}, now is ${new Date(now * 1e3).toISOString()}`
87
- };
88
- }
89
- if (pr.claims.exp !== void 0 && pr.claims.exp < now - maxClockSkew) {
90
- return {
91
- valid: false,
92
- code: "E_EXPIRED",
93
- message: `Receipt expired at ${new Date(pr.claims.exp * 1e3).toISOString()}`
94
- };
95
- }
96
- if (pr.variant === "commerce") {
79
+ if (pr.wireVersion === "0.2") {
97
80
  const claims = pr.claims;
98
- if (subjectUri !== void 0 && claims.subject?.uri !== subjectUri) {
81
+ if (issuer !== void 0 && claims.iss !== issuer) {
99
82
  return {
100
83
  valid: false,
101
- code: "E_INVALID_SUBJECT",
102
- message: `Subject mismatch: expected "${subjectUri}", got "${claims.subject?.uri ?? "undefined"}"`
84
+ code: "E_INVALID_ISSUER",
85
+ message: `Issuer mismatch: expected "${issuer}", got "${claims.iss}"`
103
86
  };
104
87
  }
105
- return {
106
- valid: true,
107
- variant: "commerce",
108
- claims,
109
- kid: result.header.kid,
110
- policy_binding: "unavailable"
111
- };
112
- } else {
113
- const claims = pr.claims;
114
88
  if (subjectUri !== void 0 && claims.sub !== subjectUri) {
115
89
  return {
116
90
  valid: false,
@@ -118,16 +92,90 @@ async function verifyLocal(jws, publicKey, options = {}) {
118
92
  message: `Subject mismatch: expected "${subjectUri}", got "${claims.sub ?? "undefined"}"`
119
93
  };
120
94
  }
95
+ if (claims.iat > now + maxClockSkew) {
96
+ return {
97
+ valid: false,
98
+ code: "E_NOT_YET_VALID",
99
+ message: `Receipt not yet valid: issued at ${new Date(claims.iat * 1e3).toISOString()}, now is ${new Date(now * 1e3).toISOString()}`
100
+ };
101
+ }
102
+ if (claims.kind === "evidence") {
103
+ const skewResult = schema.checkOccurredAtSkew(claims.occurred_at, claims.iat, now, maxClockSkew);
104
+ if (skewResult === "future_error") {
105
+ return {
106
+ valid: false,
107
+ code: "E_OCCURRED_AT_FUTURE",
108
+ message: `occurred_at is in the future beyond tolerance (${maxClockSkew}s)`
109
+ };
110
+ }
111
+ if (skewResult !== null) {
112
+ accumulatedWarnings.push(skewResult);
113
+ }
114
+ }
115
+ if (!schema.REGISTERED_RECEIPT_TYPES.has(claims.type)) {
116
+ accumulatedWarnings.push({
117
+ code: schema.WARNING_TYPE_UNREGISTERED,
118
+ message: "Receipt type is not in the recommended type registry",
119
+ pointer: "/type"
120
+ });
121
+ }
122
+ if (claims.extensions !== void 0) {
123
+ for (const key of Object.keys(claims.extensions)) {
124
+ if (!schema.REGISTERED_EXTENSION_GROUP_KEYS.has(key) && schema.isValidExtensionKey(key)) {
125
+ const escapedKey = key.replace(/~/g, "~0").replace(/\//g, "~1");
126
+ accumulatedWarnings.push({
127
+ code: schema.WARNING_UNKNOWN_EXTENSION,
128
+ message: "Unknown extension key preserved without schema validation",
129
+ pointer: `/extensions/${escapedKey}`
130
+ });
131
+ }
132
+ }
133
+ }
134
+ if (policyDigest !== void 0 && !kernel.HASH.pattern.test(policyDigest)) {
135
+ return {
136
+ valid: false,
137
+ code: "E_INVALID_FORMAT",
138
+ message: "policyDigest option must be in sha256:<64 lowercase hex> format"
139
+ };
140
+ }
141
+ const receiptPolicyDigest = claims.policy?.digest;
142
+ const bindingStatus = receiptPolicyDigest === void 0 || policyDigest === void 0 ? "unavailable" : schema.verifyPolicyBinding(receiptPolicyDigest, policyDigest);
143
+ if (bindingStatus === "failed") {
144
+ return {
145
+ valid: false,
146
+ code: "E_POLICY_BINDING_FAILED",
147
+ message: "Policy binding check failed: receipt policy digest does not match local policy",
148
+ details: {
149
+ receipt_policy_digest: receiptPolicyDigest,
150
+ local_policy_digest: policyDigest,
151
+ ...claims.policy?.uri !== void 0 && { policy_uri: claims.policy.uri }
152
+ }
153
+ };
154
+ }
121
155
  return {
122
156
  valid: true,
123
- variant: "attestation",
157
+ variant: "wire-02",
124
158
  claims,
125
159
  kid: result.header.kid,
126
- policy_binding: "unavailable"
160
+ wireVersion: "0.2",
161
+ warnings: schema.sortWarnings(accumulatedWarnings),
162
+ policy_binding: bindingStatus
127
163
  };
128
164
  }
165
+ return {
166
+ valid: false,
167
+ code: "E_UNSUPPORTED_WIRE_VERSION",
168
+ message: "Wire 0.1 receipts are not supported. Re-issue as Wire 0.2 using issueWire02()."
169
+ };
129
170
  } catch (err) {
130
171
  if (isCryptoError(err)) {
172
+ if (Object.prototype.hasOwnProperty.call(JOSE_CODE_MAP, err.code)) {
173
+ return {
174
+ valid: false,
175
+ code: JOSE_CODE_MAP[err.code],
176
+ message: err.message
177
+ };
178
+ }
131
179
  if (FORMAT_ERROR_CODES.has(err.code)) {
132
180
  return {
133
181
  valid: false,
@@ -142,6 +190,13 @@ async function verifyLocal(jws, publicKey, options = {}) {
142
190
  message: err.message
143
191
  };
144
192
  }
193
+ if (err.code === "CRYPTO_WIRE_VERSION_MISMATCH") {
194
+ return {
195
+ valid: false,
196
+ code: "E_WIRE_VERSION_MISMATCH",
197
+ message: err.message
198
+ };
199
+ }
145
200
  }
146
201
  if (err !== null && typeof err === "object" && "name" in err && err.name === "SyntaxError") {
147
202
  const syntaxMessage = "message" in err && typeof err.message === "string" ? err.message : "Invalid JSON";
@@ -160,14 +215,18 @@ async function verifyLocal(jws, publicKey, options = {}) {
160
215
  }
161
216
  }
162
217
  function isCommerceResult(r) {
163
- return r.valid === true && r.variant === "commerce";
218
+ return false;
164
219
  }
165
220
  function isAttestationResult(r) {
166
- return r.valid === true && r.variant === "attestation";
221
+ return false;
222
+ }
223
+ function isWire02Result(r) {
224
+ return r.valid === true && r.variant === "wire-02";
167
225
  }
168
226
 
169
227
  exports.isAttestationResult = isAttestationResult;
170
228
  exports.isCommerceResult = isCommerceResult;
229
+ exports.isWire02Result = isWire02Result;
171
230
  exports.verifyLocal = verifyLocal;
172
231
  //# sourceMappingURL=verify-local.cjs.map
173
232
  //# sourceMappingURL=verify-local.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/verify-local.ts"],"names":["jwsVerify","validateKernelConstraints","parseReceiptClaims"],"mappings":"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","file":"verify-local.cjs","sourcesContent":["/**\n * Local receipt verification with schema validation\n *\n * Use this for verifying receipts when you have the public key locally,\n * without JWKS discovery.\n */\n\nimport { verify as jwsVerify } from '@peac/crypto';\nimport {\n parseReceiptClaims,\n validateKernelConstraints,\n type ReceiptClaimsType,\n type AttestationReceiptClaims,\n} from '@peac/schema';\nimport type { PolicyBindingStatus } from './verifier-types';\n\n/**\n * Structural type for CryptoError\n * Used instead of instanceof for robustness across ESM/CJS boundaries\n */\ninterface CryptoErrorLike {\n name: 'CryptoError';\n code: string;\n message: string;\n}\n\n/**\n * Structural check for CryptoError\n * More robust than instanceof across module boundaries (ESM/CJS, duplicate packages)\n */\nfunction isCryptoError(err: unknown): err is CryptoErrorLike {\n return (\n err !== null &&\n typeof err === 'object' &&\n 'name' in err &&\n err.name === 'CryptoError' &&\n 'code' in err &&\n typeof err.code === 'string' &&\n err.code.startsWith('CRYPTO_') &&\n 'message' in err &&\n typeof err.message === 'string'\n );\n}\n\n/**\n * Canonical error codes for local verification\n *\n * These map to E_* codes in specs/kernel/errors.json\n */\nexport type VerifyLocalErrorCode =\n | 'E_INVALID_SIGNATURE'\n | 'E_INVALID_FORMAT'\n | 'E_CONSTRAINT_VIOLATION'\n | 'E_EXPIRED'\n | 'E_NOT_YET_VALID'\n | 'E_INVALID_ISSUER'\n | 'E_INVALID_AUDIENCE'\n | 'E_INVALID_SUBJECT'\n | 'E_INVALID_RECEIPT_ID'\n | 'E_MISSING_EXP'\n | 'E_INTERNAL';\n\n/**\n * Options for local verification\n */\nexport interface VerifyLocalOptions {\n /**\n * Expected issuer URL\n *\n * If provided, verification fails if receipt.iss does not match.\n */\n issuer?: string;\n\n /**\n * Expected audience URL\n *\n * If provided, verification fails if receipt.aud does not match.\n */\n audience?: string;\n\n /**\n * Expected subject URI\n *\n * If provided, verification fails if receipt.subject.uri does not match.\n * Binds the receipt to a specific resource/interaction target.\n */\n subjectUri?: string;\n\n /**\n * Expected receipt ID (rid)\n *\n * If provided, verification fails if receipt.rid does not match.\n * Useful for idempotency checks or correlating with prior receipts.\n */\n rid?: string;\n\n /**\n * Require expiration claim\n *\n * If true, receipts without exp claim are rejected.\n * Defaults to false.\n */\n requireExp?: boolean;\n\n /**\n * Current timestamp (Unix seconds)\n *\n * Defaults to Date.now() / 1000. Override for testing.\n */\n now?: number;\n\n /**\n * Maximum clock skew tolerance (seconds)\n *\n * Allows for clock drift between issuer and verifier.\n * Defaults to 300 (5 minutes).\n */\n maxClockSkew?: number;\n}\n\n/**\n * Result of successful local verification\n *\n * Discriminated union on `variant` -- callers narrow claims type via variant check:\n * if (result.valid && result.variant === 'commerce') { result.claims.amt }\n */\nexport type VerifyLocalSuccess =\n | {\n /** Verification succeeded */\n valid: true;\n /** Receipt variant (commerce = payment receipt, attestation = non-payment) */\n variant: 'commerce';\n /** Validated commerce receipt claims */\n claims: ReceiptClaimsType;\n /** Key ID from JWS header (for logging/indexing) */\n kid: string;\n /**\n * Policy binding status (DD-49).\n *\n * Always 'unavailable' for Wire 0.1 receipts (no policy digest on wire).\n * Wire 0.2 receipts with `peac.policy.digest` will report 'verified' or 'failed'.\n */\n policy_binding: PolicyBindingStatus;\n }\n | {\n /** Verification succeeded */\n valid: true;\n /** Receipt variant (commerce = payment receipt, attestation = non-payment) */\n variant: 'attestation';\n /** Validated attestation receipt claims */\n claims: AttestationReceiptClaims;\n /** Key ID from JWS header (for logging/indexing) */\n kid: string;\n /**\n * Policy binding status (DD-49).\n *\n * Always 'unavailable' for Wire 0.1 receipts (no policy digest on wire).\n * Wire 0.2 receipts with `peac.policy.digest` will report 'verified' or 'failed'.\n */\n policy_binding: PolicyBindingStatus;\n };\n\n/**\n * Result of failed local verification\n */\nexport interface VerifyLocalFailure {\n /** Verification failed */\n valid: false;\n\n /** Canonical error code (maps to specs/kernel/errors.json) */\n code: VerifyLocalErrorCode;\n\n /** Human-readable error message */\n message: string;\n\n /** Structured details for debugging (stable error code preserved in `code`) */\n details?: {\n /** Precise parse error code from unified parser (e.g. E_PARSE_COMMERCE_INVALID) */\n parse_code?: string;\n /** Zod validation issues (bounded, stable shape -- non-normative, may change) */\n issues?: ReadonlyArray<{ path: string; message: string }>;\n };\n}\n\n/**\n * Union type for local verification result\n */\nexport type VerifyLocalResult = VerifyLocalSuccess | VerifyLocalFailure;\n\n/**\n * Crypto error codes that indicate format/validation issues\n * These are CRYPTO_* internal codes from @peac/crypto, mapped to canonical E_* codes\n */\nconst FORMAT_ERROR_CODES = new Set([\n 'CRYPTO_INVALID_JWS_FORMAT',\n 'CRYPTO_INVALID_TYP',\n 'CRYPTO_INVALID_ALG',\n 'CRYPTO_INVALID_KEY_LENGTH',\n]);\n\n/** Max parse issues to include in details (prevents log bloat) */\nconst MAX_PARSE_ISSUES = 25;\n\n/**\n * Sanitize Zod issues into a bounded, stable structure.\n * Avoids exposing raw Zod internals or unbounded arrays in the public API.\n */\nfunction sanitizeParseIssues(\n issues: unknown\n): ReadonlyArray<{ path: string; message: string }> | undefined {\n if (!Array.isArray(issues)) return undefined;\n return issues.slice(0, MAX_PARSE_ISSUES).map((issue) => ({\n path: Array.isArray(issue?.path) ? issue.path.join('.') : '',\n message: typeof issue?.message === 'string' ? issue.message : String(issue),\n }));\n}\n\n/**\n * Verify a PEAC receipt locally with a known public key\n *\n * This function:\n * 1. Verifies the Ed25519 signature and header (typ, alg)\n * 2. Validates the receipt schema with Zod\n * 3. Checks issuer/audience/subject binding (if options provided)\n * 4. Checks time validity (exp/iat with clock skew tolerance)\n *\n * Use this when you have the issuer's public key and don't need JWKS discovery.\n * For JWKS-based verification, use `verifyReceipt()` instead.\n *\n * @param jws - JWS compact serialization\n * @param publicKey - Ed25519 public key (32 bytes)\n * @param options - Optional verification options (issuer, audience, subject, clock skew)\n * @returns Typed verification result\n *\n * @example\n * ```typescript\n * const result = await verifyLocal(jws, publicKey, {\n * issuer: 'https://api.example.com',\n * audience: 'https://client.example.com',\n * subjectUri: 'https://api.example.com/inference/v1',\n * });\n * if (result.valid) {\n * console.log('Issuer:', result.claims.iss);\n * console.log('Amount:', result.claims.amt, result.claims.cur);\n * console.log('Key ID:', result.kid);\n * } else {\n * console.error('Verification failed:', result.code, result.message);\n * }\n * ```\n */\nexport async function verifyLocal(\n jws: string,\n publicKey: Uint8Array,\n options: VerifyLocalOptions = {}\n): Promise<VerifyLocalResult> {\n const { issuer, audience, subjectUri, rid, requireExp = false, maxClockSkew = 300 } = options;\n const now = options.now ?? Math.floor(Date.now() / 1000);\n\n try {\n // 1. Verify signature and header (typ, alg validated by @peac/crypto)\n const result = await jwsVerify<unknown>(jws, publicKey);\n\n if (!result.valid) {\n return {\n valid: false,\n code: 'E_INVALID_SIGNATURE',\n message: 'Ed25519 signature verification failed',\n };\n }\n\n // 2. Validate structural kernel constraints (DD-121, fail-closed)\n const constraintResult = validateKernelConstraints(result.payload);\n if (!constraintResult.valid) {\n const v = constraintResult.violations[0];\n return {\n valid: false,\n code: 'E_CONSTRAINT_VIOLATION',\n message: `Kernel constraint violated: ${v.constraint} (actual: ${v.actual}, limit: ${v.limit})`,\n };\n }\n\n // 3. Validate schema (unified parser supports both commerce and attestation)\n const pr = parseReceiptClaims(result.payload);\n\n if (!pr.ok) {\n return {\n valid: false,\n code: 'E_INVALID_FORMAT',\n message: `Receipt schema validation failed: ${pr.error.message}`,\n details: { parse_code: pr.error.code, issues: sanitizeParseIssues(pr.error.issues) },\n };\n }\n\n // Shared binding checks (iss, aud, rid, iat, exp exist on both receipt types)\n // 3. Check issuer binding\n if (issuer !== undefined && pr.claims.iss !== issuer) {\n return {\n valid: false,\n code: 'E_INVALID_ISSUER',\n message: `Issuer mismatch: expected \"${issuer}\", got \"${pr.claims.iss}\"`,\n };\n }\n\n // 4. Check audience binding\n if (audience !== undefined && pr.claims.aud !== audience) {\n return {\n valid: false,\n code: 'E_INVALID_AUDIENCE',\n message: `Audience mismatch: expected \"${audience}\", got \"${pr.claims.aud}\"`,\n };\n }\n\n // 5. Check receipt ID binding\n if (rid !== undefined && pr.claims.rid !== rid) {\n return {\n valid: false,\n code: 'E_INVALID_RECEIPT_ID',\n message: `Receipt ID mismatch: expected \"${rid}\", got \"${pr.claims.rid}\"`,\n };\n }\n\n // 6. Check requireExp\n if (requireExp && pr.claims.exp === undefined) {\n return {\n valid: false,\n code: 'E_MISSING_EXP',\n message: 'Receipt missing required exp claim',\n };\n }\n\n // 7. Check not-yet-valid (iat with clock skew)\n if (pr.claims.iat > now + maxClockSkew) {\n return {\n valid: false,\n code: 'E_NOT_YET_VALID',\n message: `Receipt not yet valid: issued at ${new Date(pr.claims.iat * 1000).toISOString()}, now is ${new Date(now * 1000).toISOString()}`,\n };\n }\n\n // 8. Check expiry (with clock skew tolerance)\n if (pr.claims.exp !== undefined && pr.claims.exp < now - maxClockSkew) {\n return {\n valid: false,\n code: 'E_EXPIRED',\n message: `Receipt expired at ${new Date(pr.claims.exp * 1000).toISOString()}`,\n };\n }\n\n // 9. Subject binding + typed return (variant-branched, no unsafe casts)\n if (pr.variant === 'commerce') {\n const claims = pr.claims as ReceiptClaimsType;\n if (subjectUri !== undefined && claims.subject?.uri !== subjectUri) {\n return {\n valid: false,\n code: 'E_INVALID_SUBJECT',\n message: `Subject mismatch: expected \"${subjectUri}\", got \"${claims.subject?.uri ?? 'undefined'}\"`,\n };\n }\n // Wire 0.1: no policy digest on wire, always 'unavailable' (DD-49)\n return {\n valid: true,\n variant: 'commerce',\n claims,\n kid: result.header.kid,\n policy_binding: 'unavailable',\n };\n } else {\n const claims = pr.claims as AttestationReceiptClaims;\n if (subjectUri !== undefined && claims.sub !== subjectUri) {\n return {\n valid: false,\n code: 'E_INVALID_SUBJECT',\n message: `Subject mismatch: expected \"${subjectUri}\", got \"${claims.sub ?? 'undefined'}\"`,\n };\n }\n // Wire 0.1: no policy digest on wire, always 'unavailable' (DD-49)\n return {\n valid: true,\n variant: 'attestation',\n claims,\n kid: result.header.kid,\n policy_binding: 'unavailable',\n };\n }\n } catch (err) {\n // Handle typed CryptoError from @peac/crypto\n // Use structural check instead of instanceof for robustness across ESM/CJS boundaries\n // Map internal CRYPTO_* codes to canonical E_* codes\n if (isCryptoError(err)) {\n if (FORMAT_ERROR_CODES.has(err.code)) {\n return {\n valid: false,\n code: 'E_INVALID_FORMAT',\n message: err.message,\n };\n }\n if (err.code === 'CRYPTO_INVALID_SIGNATURE') {\n return {\n valid: false,\n code: 'E_INVALID_SIGNATURE',\n message: err.message,\n };\n }\n }\n\n // Handle JSON parse errors from malformed payloads\n // Use structural check for cross-boundary robustness (consistent with isCryptoError pattern)\n if (\n err !== null &&\n typeof err === 'object' &&\n 'name' in err &&\n (err as { name: unknown }).name === 'SyntaxError'\n ) {\n const syntaxMessage =\n 'message' in err && typeof (err as { message: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Invalid JSON';\n return {\n valid: false,\n code: 'E_INVALID_FORMAT',\n message: `Invalid receipt payload: ${syntaxMessage}`,\n };\n }\n\n // All other errors -> E_INTERNAL\n // No message parsing - code-based mapping only\n const message = err instanceof Error ? err.message : String(err);\n return {\n valid: false,\n code: 'E_INTERNAL',\n message: `Unexpected verification error: ${message}`,\n };\n }\n}\n\n/**\n * Type guard: narrows a VerifyLocalResult to a commerce success.\n *\n * Use instead of manual `result.valid && result.variant === 'commerce'` checks\n * to get proper claims narrowing to ReceiptClaimsType.\n */\nexport function isCommerceResult(\n r: VerifyLocalResult\n): r is VerifyLocalSuccess & { variant: 'commerce' } {\n return r.valid === true && r.variant === 'commerce';\n}\n\n/**\n * Type guard: narrows a VerifyLocalResult to an attestation success.\n *\n * Use instead of manual `result.valid && result.variant === 'attestation'` checks\n * to get proper claims narrowing to AttestationReceiptClaims.\n */\nexport function isAttestationResult(\n r: VerifyLocalResult\n): r is VerifyLocalSuccess & { variant: 'attestation' } {\n return r.valid === true && r.variant === 'attestation';\n}\n"]}
1
+ {"version":3,"sources":["../src/verify-local.ts"],"names":["jwsVerify","WARNING_TYP_MISSING","validateKernelConstraints","parseReceiptClaims","checkOccurredAtSkew","REGISTERED_RECEIPT_TYPES","WARNING_TYPE_UNREGISTERED","REGISTERED_EXTENSION_GROUP_KEYS","isValidExtensionKey","WARNING_UNKNOWN_EXTENSION","HASH","verifyPolicyBinding","sortWarnings"],"mappings":"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","file":"verify-local.cjs","sourcesContent":["/**\n * Local receipt verification with schema validation\n *\n * Use this for verifying receipts when you have the public key locally,\n * without JWKS discovery.\n */\n\nimport { verify as jwsVerify } from '@peac/crypto';\nimport { type VerificationStrictness, type VerificationWarning, HASH } from '@peac/kernel';\nimport {\n parseReceiptClaims,\n validateKernelConstraints,\n type Wire02Claims,\n checkOccurredAtSkew,\n sortWarnings,\n WARNING_TYP_MISSING,\n WARNING_TYPE_UNREGISTERED,\n WARNING_UNKNOWN_EXTENSION,\n REGISTERED_RECEIPT_TYPES,\n REGISTERED_EXTENSION_GROUP_KEYS,\n isValidExtensionKey,\n verifyPolicyBinding,\n} from '@peac/schema';\nimport type { PolicyBindingStatus } from './verifier-types';\n\n/**\n * Structural type for CryptoError\n * Used instead of instanceof for robustness across ESM/CJS boundaries\n */\ninterface CryptoErrorLike {\n name: 'CryptoError';\n code: string;\n message: string;\n}\n\n/**\n * Structural check for CryptoError\n * More robust than instanceof across module boundaries (ESM/CJS, duplicate packages)\n */\nfunction isCryptoError(err: unknown): err is CryptoErrorLike {\n return (\n err !== null &&\n typeof err === 'object' &&\n 'name' in err &&\n err.name === 'CryptoError' &&\n 'code' in err &&\n typeof err.code === 'string' &&\n err.code.startsWith('CRYPTO_') &&\n 'message' in err &&\n typeof err.message === 'string'\n );\n}\n\n/**\n * Canonical error codes for local verification\n *\n * These map to E_* codes in specs/kernel/errors.json.\n * JOSE hardening codes (E_JWS_*) are distinct from generic E_INVALID_FORMAT\n * so callers can distinguish key-injection, compression, and crit attacks from\n * ordinary format errors (v0.12.0-preview.1, DD-156).\n */\nexport type VerifyLocalErrorCode =\n | 'E_INVALID_SIGNATURE'\n | 'E_INVALID_FORMAT'\n | 'E_CONSTRAINT_VIOLATION'\n | 'E_EXPIRED'\n | 'E_NOT_YET_VALID'\n | 'E_INVALID_ISSUER'\n | 'E_INVALID_AUDIENCE'\n | 'E_INVALID_SUBJECT'\n | 'E_INVALID_RECEIPT_ID'\n | 'E_MISSING_EXP'\n | 'E_WIRE_VERSION_MISMATCH'\n | 'E_UNSUPPORTED_WIRE_VERSION'\n | 'E_OCCURRED_AT_FUTURE'\n // JOSE hardening codes (Wire 0.2, v0.12.0-preview.1, DD-156)\n | 'E_JWS_EMBEDDED_KEY'\n | 'E_JWS_CRIT_REJECTED'\n | 'E_JWS_MISSING_KID'\n | 'E_JWS_B64_REJECTED'\n | 'E_JWS_ZIP_REJECTED'\n // Policy binding (Wire 0.2, v0.12.0-preview.1, DD-151)\n | 'E_POLICY_BINDING_FAILED'\n | 'E_INTERNAL';\n\n/**\n * Options for local verification\n */\nexport interface VerifyLocalOptions {\n /**\n * Expected issuer URL\n *\n * If provided, verification fails if receipt.iss does not match.\n */\n issuer?: string;\n\n /**\n * @deprecated Wire 0.2 does not have an `aud` claim. This option is ignored.\n * Retained for source compatibility during migration; will be removed in v1.0.\n */\n audience?: string;\n\n /**\n * Expected subject URI\n *\n * If provided, verification fails if receipt.sub does not match.\n * Binds the receipt to a specific resource/interaction target.\n */\n subjectUri?: string;\n\n /**\n * @deprecated Wire 0.2 does not have a `rid` claim. Use `jti` for receipt identification.\n * This option is ignored. Retained for source compatibility; will be removed in v1.0.\n */\n rid?: string;\n\n /**\n * @deprecated Wire 0.2 receipts do not expire (permanent evidence by design).\n * This option is ignored. Retained for source compatibility; will be removed in v1.0.\n */\n requireExp?: boolean;\n\n /**\n * Current timestamp (Unix seconds)\n *\n * Defaults to Date.now() / 1000. Override for testing.\n */\n now?: number;\n\n /**\n * Maximum clock skew tolerance (seconds)\n *\n * Allows for clock drift between issuer and verifier.\n * Defaults to 300 (5 minutes).\n */\n maxClockSkew?: number;\n\n /**\n * Verification strictness profile (v0.12.0-preview.1, DD-156).\n *\n * - 'strict' (default): missing typ is a hard error before schema validation.\n * - 'interop': missing typ emits a 'typ_missing' warning and routes by payload content.\n *\n * Strictness is EXCLUSIVELY controlled here (@peac/protocol). @peac/crypto has no strictness param.\n */\n strictness?: VerificationStrictness;\n\n /**\n * Pre-computed local policy digest for policy binding (Wire 0.2, v0.12.0-preview.1, DD-151).\n *\n * Must be in 'sha256:<64 lowercase hex>' format, computed via computePolicyDigestJcs()\n * from @peac/protocol. When provided alongside a receipt that contains a policy block\n * (policy.digest), the binding check is performed:\n * - Match: policy_binding = 'verified'\n * - Mismatch: hard fail with E_POLICY_BINDING_FAILED\n * - Either absent: policy_binding = 'unavailable'\n *\n * Always 'unavailable' for Wire 0.1 receipts regardless of this option.\n */\n policyDigest?: string;\n}\n\n/**\n * Result of successful local verification (Wire 0.2 only)\n *\n * Wire 0.1 receipts are no longer accepted by verifyLocal() and return\n * E_UNSUPPORTED_WIRE_VERSION. Re-issue as Wire 0.2 using issueWire02().\n */\nexport interface VerifyLocalSuccess {\n /** Verification succeeded */\n valid: true;\n /** Receipt variant (always 'wire-02') */\n variant: 'wire-02';\n /** Validated Wire 0.2 receipt claims */\n claims: Wire02Claims;\n /** Key ID from JWS header (for logging/indexing) */\n kid: string;\n /** Wire format version */\n wireVersion: '0.2';\n /** Verification warnings from schema parsing and strictness routing */\n warnings: VerificationWarning[];\n /**\n * Policy binding status (DD-49, DD-151).\n *\n * Three-state result:\n * - 'unavailable': either the receipt contains no policy block, or the\n * caller did not pass a policyDigest option to verifyLocal(). No check.\n * - 'verified': both digests present and match exactly.\n * - 'failed': not returned on success; verifyLocal() returns\n * E_POLICY_BINDING_FAILED (valid: false) before reaching this field.\n */\n policy_binding: PolicyBindingStatus;\n}\n\n/**\n * Result of failed local verification\n */\nexport interface VerifyLocalFailure {\n /** Verification failed */\n valid: false;\n\n /** Canonical error code (maps to specs/kernel/errors.json) */\n code: VerifyLocalErrorCode;\n\n /** Human-readable error message */\n message: string;\n\n /** Structured details for debugging (stable error code preserved in `code`) */\n details?: {\n /** Precise parse error code from unified parser (e.g. E_PARSE_COMMERCE_INVALID) */\n parse_code?: string;\n /** Zod validation issues (bounded, stable shape; non-normative, may change) */\n issues?: ReadonlyArray<{ path: string; message: string }>;\n /**\n * Policy digest from the receipt (present when code is E_POLICY_BINDING_FAILED).\n * Both are SHA-256 hashes; safe to log without leaking policy content.\n */\n receipt_policy_digest?: string;\n /** Caller-supplied policy digest (present when code is E_POLICY_BINDING_FAILED). */\n local_policy_digest?: string;\n /** policy.uri hint from the receipt (present when code is E_POLICY_BINDING_FAILED and uri set). */\n policy_uri?: string;\n };\n}\n\n/**\n * Union type for local verification result\n */\nexport type VerifyLocalResult = VerifyLocalSuccess | VerifyLocalFailure;\n\n/**\n * Internal CRYPTO_* codes that map to generic E_INVALID_FORMAT.\n * These are format/encoding errors not security-specific.\n */\nconst FORMAT_ERROR_CODES = new Set([\n 'CRYPTO_INVALID_JWS_FORMAT',\n 'CRYPTO_INVALID_TYP',\n 'CRYPTO_INVALID_ALG',\n 'CRYPTO_INVALID_KEY_LENGTH',\n]);\n\n/**\n * JOSE hardening code mapping: CRYPTO_JWS_* → specific E_JWS_* (v0.12.0-preview.1, DD-156).\n *\n * Each JOSE hazard code maps to its specific public E_JWS_* counterpart rather than\n * collapsing into the generic E_INVALID_FORMAT. This lets callers distinguish embedded-key\n * injection, crit-header abuse, and unencoded-payload attacks from ordinary format errors.\n */\nconst JOSE_CODE_MAP: Record<string, VerifyLocalErrorCode> = {\n CRYPTO_JWS_EMBEDDED_KEY: 'E_JWS_EMBEDDED_KEY',\n CRYPTO_JWS_CRIT_REJECTED: 'E_JWS_CRIT_REJECTED',\n CRYPTO_JWS_MISSING_KID: 'E_JWS_MISSING_KID',\n CRYPTO_JWS_B64_REJECTED: 'E_JWS_B64_REJECTED',\n CRYPTO_JWS_ZIP_REJECTED: 'E_JWS_ZIP_REJECTED',\n};\n\n/** Max parse issues to include in details (prevents log bloat) */\nconst MAX_PARSE_ISSUES = 25;\n\n/**\n * Sanitize Zod issues into a bounded, stable structure.\n * Avoids exposing raw Zod internals or unbounded arrays in the public API.\n */\nfunction sanitizeParseIssues(\n issues: unknown\n): ReadonlyArray<{ path: string; message: string }> | undefined {\n if (!Array.isArray(issues)) return undefined;\n return issues.slice(0, MAX_PARSE_ISSUES).map((issue) => ({\n path: Array.isArray(issue?.path) ? issue.path.join('.') : '',\n message: typeof issue?.message === 'string' ? issue.message : String(issue),\n }));\n}\n\n/**\n * Verify a Wire 0.2 PEAC receipt locally with a known public key.\n *\n * Wire 0.2 only: Wire 0.1 receipts return E_UNSUPPORTED_WIRE_VERSION.\n * Re-issue Wire 0.1 receipts as Wire 0.2 using issueWire02().\n *\n * This function:\n * 1. Verifies the Ed25519 signature and header (typ, alg)\n * 2. Applies strictness routing for missing typ (strict: hard error; interop: warning)\n * 3. Validates the receipt schema with Zod (Wire 0.2 only)\n * 4. Checks issuer/subject binding (if options provided)\n * 5. Checks time validity (iat with clock skew tolerance)\n * 6. Checks occurred_at skew and collects parse warnings\n *\n * @param jws - JWS compact serialization\n * @param publicKey - Ed25519 public key (32 bytes)\n * @param options - Optional verification options (issuer, subject, clock skew, strictness, policyDigest)\n * @returns Typed verification result\n *\n * @example\n * ```typescript\n * const result = await verifyLocal(jws, publicKey, {\n * issuer: 'https://api.example.com',\n * strictness: 'strict',\n * });\n * if (result.valid) {\n * console.log('Kind:', result.claims.kind);\n * console.log('Warnings:', result.warnings);\n * }\n * ```\n */\nexport async function verifyLocal(\n jws: string,\n publicKey: Uint8Array,\n options: VerifyLocalOptions = {}\n): Promise<VerifyLocalResult> {\n const { issuer, subjectUri, maxClockSkew = 300, strictness = 'strict', policyDigest } = options;\n const now = options.now ?? Math.floor(Date.now() / 1000);\n\n try {\n // 1. Verify signature and header (typ, alg validated by @peac/crypto)\n const result = await jwsVerify<unknown>(jws, publicKey);\n\n if (!result.valid) {\n return {\n valid: false,\n code: 'E_INVALID_SIGNATURE',\n message: 'Ed25519 signature verification failed',\n };\n }\n\n // Accumulated warnings for Wire 0.2 path\n const accumulatedWarnings: VerificationWarning[] = [];\n\n // 2. Strictness routing for missing typ (Correction 1, DD-156)\n if (result.header.typ === undefined) {\n if (strictness === 'strict') {\n return {\n valid: false,\n code: 'E_INVALID_FORMAT',\n message: 'Missing JWS typ header: strict mode requires typ to be present',\n };\n }\n // interop mode: emit warning and continue\n accumulatedWarnings.push({\n code: WARNING_TYP_MISSING,\n message: 'JWS typ header is absent; accepted in interop mode',\n });\n }\n\n // 3. Validate structural kernel constraints (DD-121, fail-closed)\n const constraintResult = validateKernelConstraints(result.payload);\n if (!constraintResult.valid) {\n const v = constraintResult.violations[0];\n return {\n valid: false,\n code: 'E_CONSTRAINT_VIOLATION',\n message: `Kernel constraint violated: ${v.constraint} (actual: ${v.actual}, limit: ${v.limit})`,\n };\n }\n\n // 4. Validate schema (unified parser supports Wire 0.1 and Wire 0.2)\n const pr = parseReceiptClaims(result.payload);\n\n if (!pr.ok) {\n return {\n valid: false,\n code: 'E_INVALID_FORMAT',\n message: `Receipt schema validation failed: ${pr.error.message}`,\n details: { parse_code: pr.error.code, issues: sanitizeParseIssues(pr.error.issues) },\n };\n }\n\n // 5. Collect parser warnings (Wire 0.2 parser may emit type/extension warnings)\n if (pr.wireVersion === '0.2') {\n accumulatedWarnings.push(...pr.warnings);\n }\n\n // Wire 0.2 path\n if (pr.wireVersion === '0.2') {\n const claims = pr.claims as Wire02Claims;\n\n // Issuer check\n if (issuer !== undefined && claims.iss !== issuer) {\n return {\n valid: false,\n code: 'E_INVALID_ISSUER',\n message: `Issuer mismatch: expected \"${issuer}\", got \"${claims.iss}\"`,\n };\n }\n\n // Subject check\n if (subjectUri !== undefined && claims.sub !== subjectUri) {\n return {\n valid: false,\n code: 'E_INVALID_SUBJECT',\n message: `Subject mismatch: expected \"${subjectUri}\", got \"${claims.sub ?? 'undefined'}\"`,\n };\n }\n\n // iat: not-yet-valid check (with clock skew)\n if (claims.iat > now + maxClockSkew) {\n return {\n valid: false,\n code: 'E_NOT_YET_VALID',\n message: `Receipt not yet valid: issued at ${new Date(claims.iat * 1000).toISOString()}, now is ${new Date(now * 1000).toISOString()}`,\n };\n }\n\n // occurred_at skew check (evidence kind only)\n if (claims.kind === 'evidence') {\n const skewResult = checkOccurredAtSkew(claims.occurred_at, claims.iat, now, maxClockSkew);\n if (skewResult === 'future_error') {\n return {\n valid: false,\n code: 'E_OCCURRED_AT_FUTURE',\n message: `occurred_at is in the future beyond tolerance (${maxClockSkew}s)`,\n };\n }\n if (skewResult !== null) {\n accumulatedWarnings.push(skewResult);\n }\n }\n\n // Emit type_unregistered warning for valid-but-unregistered type values (DD-155)\n if (!REGISTERED_RECEIPT_TYPES.has(claims.type)) {\n accumulatedWarnings.push({\n code: WARNING_TYPE_UNREGISTERED,\n message: 'Receipt type is not in the recommended type registry',\n pointer: '/type',\n });\n }\n\n // Emit unknown_extension_preserved warnings for unrecognized-but-well-formed keys (DD-155)\n // Malformed keys are already hard errors (E_INVALID_EXTENSION_KEY) at schema layer.\n if (claims.extensions !== undefined) {\n for (const key of Object.keys(claims.extensions)) {\n if (!REGISTERED_EXTENSION_GROUP_KEYS.has(key) && isValidExtensionKey(key)) {\n // RFC 6901: '~' -> '~0', '/' -> '~1'\n const escapedKey = key.replace(/~/g, '~0').replace(/\\//g, '~1');\n accumulatedWarnings.push({\n code: WARNING_UNKNOWN_EXTENSION,\n message: 'Unknown extension key preserved without schema validation',\n pointer: `/extensions/${escapedKey}`,\n });\n }\n }\n }\n\n // Validate policyDigest option format (DD-151): must be sha256:<64 lowercase hex> if provided.\n if (policyDigest !== undefined && !HASH.pattern.test(policyDigest)) {\n return {\n valid: false,\n code: 'E_INVALID_FORMAT',\n message: 'policyDigest option must be in sha256:<64 lowercase hex> format',\n };\n }\n\n // Policy binding check (DD-151): 3-state result.\n // 'unavailable' when either receipt has no policy block or caller omitted policyDigest.\n // 'verified' / 'failed' when both are present; 'failed' is a hard verification error.\n const receiptPolicyDigest = claims.policy?.digest;\n const bindingStatus: PolicyBindingStatus =\n receiptPolicyDigest === undefined || policyDigest === undefined\n ? 'unavailable'\n : verifyPolicyBinding(receiptPolicyDigest, policyDigest);\n if (bindingStatus === 'failed') {\n return {\n valid: false,\n code: 'E_POLICY_BINDING_FAILED',\n message: 'Policy binding check failed: receipt policy digest does not match local policy',\n details: {\n receipt_policy_digest: receiptPolicyDigest,\n local_policy_digest: policyDigest,\n ...(claims.policy?.uri !== undefined && { policy_uri: claims.policy.uri }),\n },\n };\n }\n\n return {\n valid: true,\n variant: 'wire-02',\n claims,\n kid: result.header.kid,\n wireVersion: '0.2',\n warnings: sortWarnings(accumulatedWarnings),\n policy_binding: bindingStatus,\n };\n }\n\n // Wire 0.1 receipts: reject with E_UNSUPPORTED_WIRE_VERSION.\n return {\n valid: false,\n code: 'E_UNSUPPORTED_WIRE_VERSION',\n message: 'Wire 0.1 receipts are not supported. Re-issue as Wire 0.2 using issueWire02().',\n };\n } catch (err) {\n // Handle typed CryptoError from @peac/crypto\n // Use structural check instead of instanceof for robustness across ESM/CJS boundaries\n // Map internal CRYPTO_* codes to canonical E_* codes.\n // JOSE hardening codes get specific E_JWS_* (not generic E_INVALID_FORMAT) so callers\n // can distinguish key-injection attacks from ordinary encoding errors.\n if (isCryptoError(err)) {\n // 1. JOSE hardening: specific E_JWS_* codes (checked first)\n if (Object.prototype.hasOwnProperty.call(JOSE_CODE_MAP, err.code)) {\n return {\n valid: false,\n code: JOSE_CODE_MAP[err.code]!,\n message: err.message,\n };\n }\n // 2. Generic format errors\n if (FORMAT_ERROR_CODES.has(err.code)) {\n return {\n valid: false,\n code: 'E_INVALID_FORMAT',\n message: err.message,\n };\n }\n if (err.code === 'CRYPTO_INVALID_SIGNATURE') {\n return {\n valid: false,\n code: 'E_INVALID_SIGNATURE',\n message: err.message,\n };\n }\n if (err.code === 'CRYPTO_WIRE_VERSION_MISMATCH') {\n return {\n valid: false,\n code: 'E_WIRE_VERSION_MISMATCH',\n message: err.message,\n };\n }\n }\n\n // Handle JSON parse errors from malformed payloads\n // Use structural check for cross-boundary robustness (consistent with isCryptoError pattern)\n if (\n err !== null &&\n typeof err === 'object' &&\n 'name' in err &&\n (err as { name: unknown }).name === 'SyntaxError'\n ) {\n const syntaxMessage =\n 'message' in err && typeof (err as { message: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Invalid JSON';\n return {\n valid: false,\n code: 'E_INVALID_FORMAT',\n message: `Invalid receipt payload: ${syntaxMessage}`,\n };\n }\n\n // All other errors -> E_INTERNAL\n // No message parsing - code-based mapping only\n const message = err instanceof Error ? err.message : String(err);\n return {\n valid: false,\n code: 'E_INTERNAL',\n message: `Unexpected verification error: ${message}`,\n };\n }\n}\n\n/**\n * @deprecated Removed: verifyLocal() is Wire 0.2 only and always returns variant 'wire-02'.\n * This guard always returns false. Remove usage and use isWire02Result() instead.\n */\nexport function isCommerceResult(\n r: VerifyLocalResult\n): r is VerifyLocalSuccess & { variant: 'wire-02' } {\n // Always false: verifyLocal() only returns variant 'wire-02'\n return false;\n}\n\n/**\n * @deprecated Removed: verifyLocal() is Wire 0.2 only and always returns variant 'wire-02'.\n * This guard always returns false. Remove usage and use isWire02Result() instead.\n */\nexport function isAttestationResult(\n r: VerifyLocalResult\n): r is VerifyLocalSuccess & { variant: 'wire-02' } {\n // Always false: verifyLocal() only returns variant 'wire-02'\n return false;\n}\n\n/**\n * Type guard: narrows a VerifyLocalResult to a Wire 0.2 success (v0.12.0-preview.1).\n *\n * Use instead of manual `result.valid && result.variant === 'wire-02'` checks\n * to get proper claims narrowing to Wire02Claims.\n */\nexport function isWire02Result(\n r: VerifyLocalResult\n): r is VerifyLocalSuccess & { variant: 'wire-02' } {\n return r.valid === true && r.variant === 'wire-02';\n}\n"]}