@peac/protocol 0.10.14 → 0.11.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.
@@ -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,EAKtB,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,CA2KvE;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":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Shared JWKS Resolver
3
+ *
4
+ * Centralizes JWKS resolution for both verify.ts and verifier-core.ts:
5
+ * 1. Fetch peac-issuer.json from issuer origin (SSRF-safe)
6
+ * 2. Validate issuer config (schema, issuer match)
7
+ * 3. Validate jwks_uri is HTTPS (protocol-level enforcement)
8
+ * 4. Fetch JWKS from jwks_uri (SSRF-safe, 64KB cap)
9
+ * 5. Validate JWKS shape
10
+ *
11
+ * No fallback paths: peac-issuer.json with jwks_uri is the only
12
+ * supported key discovery mechanism.
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ import type { SSRFFetchError } from './ssrf-safe-fetch.js';
17
+ /**
18
+ * JWK structure for Ed25519 keys
19
+ */
20
+ export interface JWK {
21
+ kty: string;
22
+ crv: string;
23
+ x: string;
24
+ kid: string;
25
+ }
26
+ /**
27
+ * JWKS document
28
+ */
29
+ export interface JWKS {
30
+ keys: JWK[];
31
+ }
32
+ /**
33
+ * Successful JWKS resolution
34
+ */
35
+ export interface JWKSResolveSuccess {
36
+ ok: true;
37
+ jwks: JWKS;
38
+ fromCache: boolean;
39
+ /** Raw JWKS bytes for digest computation (only present when not from cache) */
40
+ rawBytes?: Uint8Array;
41
+ }
42
+ /**
43
+ * JWKS resolution error
44
+ */
45
+ export interface JWKSResolveError {
46
+ ok: false;
47
+ /** Kernel error code */
48
+ code: string;
49
+ /** Human-readable message */
50
+ message: string;
51
+ /** Original SSRF reason (preserved for diagnostic fidelity) */
52
+ reason?: SSRFFetchError['reason'];
53
+ /** Blocked URL (if applicable) */
54
+ blockedUrl?: string;
55
+ }
56
+ export type JWKSResolveResult = JWKSResolveSuccess | JWKSResolveError;
57
+ /**
58
+ * Options for JWKS resolution
59
+ */
60
+ export interface ResolveJWKSOptions {
61
+ /** Cache TTL in milliseconds (default: 300000 = 5 minutes) */
62
+ cacheTtlMs?: number;
63
+ /** Maximum cache entries before LRU eviction (default: 1000) */
64
+ maxCacheEntries?: number;
65
+ /** Bypass cache entirely (default: false) */
66
+ noCache?: boolean;
67
+ }
68
+ /**
69
+ * Clear the shared JWKS cache
70
+ */
71
+ export declare function clearJWKSCache(): void;
72
+ /**
73
+ * Get JWKS cache size (for testing)
74
+ * @internal
75
+ */
76
+ export declare function getJWKSCacheSize(): number;
77
+ /**
78
+ * Resolve JWKS for an issuer using strict discovery:
79
+ * peac-issuer.json -> jwks_uri -> JWKS
80
+ *
81
+ * No fallback to direct JWKS or peac.txt key discovery.
82
+ *
83
+ * @param issuerUrl - Issuer origin URL (e.g. "https://api.example.com")
84
+ * @param options - Cache and resolution options
85
+ * @returns Resolved JWKS or error
86
+ */
87
+ export declare function resolveJWKS(issuerUrl: string, options?: ResolveJWKSOptions): Promise<JWKSResolveResult>;
88
+ //# sourceMappingURL=jwks-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwks-resolver.d.ts","sourceRoot":"","sources":["../src/jwks-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAO3D;;GAEG;AACH,MAAM,WAAW,GAAG;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,EAAE,MAAM,CAAC;IACV,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,GAAG,EAAE,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,IAAI,CAAC;IACX,SAAS,EAAE,OAAO,CAAC;IACnB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,UAAU,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,KAAK,CAAC;IACV,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC;IAClC,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,GAAG,gBAAgB,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA6CD;;GAEG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAyFD;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CAoJ5B"}
@@ -51,12 +51,4 @@ export interface VerifyCoreResult {
51
51
  * 10. extensions.limits - Check extension sizes
52
52
  */
53
53
  export declare function verifyReceiptCore(options: VerifyCoreOptions): Promise<VerifyCoreResult>;
54
- /**
55
- * Clear the JWKS cache
56
- */
57
- export declare function clearJWKSCache(): void;
58
- /**
59
- * Get JWKS cache size (for testing)
60
- */
61
- export declare function getJWKSCacheSize(): number;
62
54
  //# sourceMappingURL=verifier-core.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"verifier-core.d.ts","sourceRoot":"","sources":["../src/verifier-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH,OAAO,EAAE,iBAAiB,EAAiB,MAAM,cAAc,CAAC;AAIhE,OAAO,KAAK,EAAa,kBAAkB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAqCzF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,0BAA0B;IAC1B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qCAAqC;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,0BAA0B;IAC1B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,+BAA+B;IAC/B,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAmLD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAob7F;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC"}
1
+ {"version":3,"file":"verifier-core.d.ts","sourceRoot":"","sources":["../src/verifier-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH,OAAO,EAAE,iBAAiB,EAAiB,MAAM,cAAc,CAAC;AAIhE,OAAO,KAAK,EAAa,kBAAkB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAqCzF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,0BAA0B;IAC1B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qCAAqC;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,0BAA0B;IAC1B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,+BAA+B;IAC/B,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AA+FD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAob7F"}
@@ -33,6 +33,15 @@ async function verifyLocal(jws, publicKey, options = {}) {
33
33
  message: "Ed25519 signature verification failed"
34
34
  };
35
35
  }
36
+ const constraintResult = schema.validateKernelConstraints(result.payload);
37
+ if (!constraintResult.valid) {
38
+ const v = constraintResult.violations[0];
39
+ return {
40
+ valid: false,
41
+ code: "E_CONSTRAINT_VIOLATION",
42
+ message: `Kernel constraint violated: ${v.constraint} (actual: ${v.actual}, limit: ${v.limit})`
43
+ };
44
+ }
36
45
  const pr = schema.parseReceiptClaims(result.payload);
37
46
  if (!pr.ok) {
38
47
  return {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/verify-local.ts"],"names":["jwsVerify","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 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_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 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","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"]}
@@ -11,7 +11,7 @@ import type { PolicyBindingStatus } from './verifier-types';
11
11
  *
12
12
  * These map to E_* codes in specs/kernel/errors.json
13
13
  */
14
- export type VerifyLocalErrorCode = 'E_INVALID_SIGNATURE' | 'E_INVALID_FORMAT' | 'E_EXPIRED' | 'E_NOT_YET_VALID' | 'E_INVALID_ISSUER' | 'E_INVALID_AUDIENCE' | 'E_INVALID_SUBJECT' | 'E_INVALID_RECEIPT_ID' | 'E_MISSING_EXP' | 'E_INTERNAL';
14
+ export type VerifyLocalErrorCode = 'E_INVALID_SIGNATURE' | 'E_INVALID_FORMAT' | 'E_CONSTRAINT_VIOLATION' | 'E_EXPIRED' | 'E_NOT_YET_VALID' | 'E_INVALID_ISSUER' | 'E_INVALID_AUDIENCE' | 'E_INVALID_SUBJECT' | 'E_INVALID_RECEIPT_ID' | 'E_MISSING_EXP' | 'E_INTERNAL';
15
15
  /**
16
16
  * Options for local verification
17
17
  */
@@ -1 +1 @@
1
- {"version":3,"file":"verify-local.d.ts","sourceRoot":"","sources":["../src/verify-local.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC9B,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AA8B5D;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAC5B,qBAAqB,GACrB,kBAAkB,GAClB,WAAW,GACX,iBAAiB,GACjB,kBAAkB,GAClB,oBAAoB,GACpB,mBAAmB,GACnB,sBAAsB,GACtB,eAAe,GACf,YAAY,CAAC;AAEjB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IACE,6BAA6B;IAC7B,KAAK,EAAE,IAAI,CAAC;IACZ,8EAA8E;IAC9E,OAAO,EAAE,UAAU,CAAC;IACpB,wCAAwC;IACxC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,cAAc,EAAE,mBAAmB,CAAC;CACrC,GACD;IACE,6BAA6B;IAC7B,KAAK,EAAE,IAAI,CAAC;IACZ,8EAA8E;IAC9E,OAAO,EAAE,aAAa,CAAC;IACvB,2CAA2C;IAC3C,MAAM,EAAE,wBAAwB,CAAC;IACjC,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,cAAc,EAAE,mBAAmB,CAAC;CACrC,CAAC;AAEN;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,KAAK,EAAE,KAAK,CAAC;IAEb,8DAA8D;IAC9D,IAAI,EAAE,oBAAoB,CAAC;IAE3B,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAEhB,+EAA+E;IAC/E,OAAO,CAAC,EAAE;QACR,mFAAmF;QACnF,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,iFAAiF;QACjF,MAAM,CAAC,EAAE,aAAa,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAC3D,CAAC;CACH;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AA8BxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,UAAU,EACrB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAwK5B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,CAAC,EAAE,iBAAiB,GACnB,CAAC,IAAI,kBAAkB,GAAG;IAAE,OAAO,EAAE,UAAU,CAAA;CAAE,CAEnD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,iBAAiB,GACnB,CAAC,IAAI,kBAAkB,GAAG;IAAE,OAAO,EAAE,aAAa,CAAA;CAAE,CAEtD"}
1
+ {"version":3,"file":"verify-local.d.ts","sourceRoot":"","sources":["../src/verify-local.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC9B,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AA8B5D;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAC5B,qBAAqB,GACrB,kBAAkB,GAClB,wBAAwB,GACxB,WAAW,GACX,iBAAiB,GACjB,kBAAkB,GAClB,oBAAoB,GACpB,mBAAmB,GACnB,sBAAsB,GACtB,eAAe,GACf,YAAY,CAAC;AAEjB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IACE,6BAA6B;IAC7B,KAAK,EAAE,IAAI,CAAC;IACZ,8EAA8E;IAC9E,OAAO,EAAE,UAAU,CAAC;IACpB,wCAAwC;IACxC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,cAAc,EAAE,mBAAmB,CAAC;CACrC,GACD;IACE,6BAA6B;IAC7B,KAAK,EAAE,IAAI,CAAC;IACZ,8EAA8E;IAC9E,OAAO,EAAE,aAAa,CAAC;IACvB,2CAA2C;IAC3C,MAAM,EAAE,wBAAwB,CAAC;IACjC,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,cAAc,EAAE,mBAAmB,CAAC;CACrC,CAAC;AAEN;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,KAAK,EAAE,KAAK,CAAC;IAEb,8DAA8D;IAC9D,IAAI,EAAE,oBAAoB,CAAC;IAE3B,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAEhB,+EAA+E;IAC/E,OAAO,CAAC,EAAE;QACR,mFAAmF;QACnF,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,iFAAiF;QACjF,MAAM,CAAC,EAAE,aAAa,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAC3D,CAAC;CACH;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AA8BxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,UAAU,EACrB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAmL5B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,CAAC,EAAE,iBAAiB,GACnB,CAAC,IAAI,kBAAkB,GAAG;IAAE,OAAO,EAAE,UAAU,CAAA;CAAE,CAEnD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,iBAAiB,GACnB,CAAC,IAAI,kBAAkB,GAAG;IAAE,OAAO,EAAE,aAAa,CAAA;CAAE,CAEtD"}
@@ -1,5 +1,5 @@
1
1
  import { verify } from '@peac/crypto';
2
- import { parseReceiptClaims } from '@peac/schema';
2
+ import { validateKernelConstraints, parseReceiptClaims } from '@peac/schema';
3
3
 
4
4
  // src/verify-local.ts
5
5
  function isCryptoError(err) {
@@ -31,6 +31,15 @@ async function verifyLocal(jws, publicKey, options = {}) {
31
31
  message: "Ed25519 signature verification failed"
32
32
  };
33
33
  }
34
+ const constraintResult = validateKernelConstraints(result.payload);
35
+ if (!constraintResult.valid) {
36
+ const v = constraintResult.violations[0];
37
+ return {
38
+ valid: false,
39
+ code: "E_CONSTRAINT_VIOLATION",
40
+ message: `Kernel constraint violated: ${v.constraint} (actual: ${v.actual}, limit: ${v.limit})`
41
+ };
42
+ }
34
43
  const pr = parseReceiptClaims(result.payload);
35
44
  if (!pr.ok) {
36
45
  return {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/verify-local.ts"],"names":["jwsVerify"],"mappings":"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","file":"verify-local.mjs","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 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_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 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"],"mappings":"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","file":"verify-local.mjs","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"]}
package/dist/verify.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  /**
2
- * Receipt verification with JWKS fetching and caching
2
+ * Receipt verification with strict issuer-config-based JWKS discovery
3
+ *
4
+ * Key discovery uses peac-issuer.json -> jwks_uri exclusively.
5
+ * No legacy fallbacks (peac.txt, direct JWKS).
6
+ * JWKS caching is centralized in jwks-resolver.ts.
3
7
  */
4
8
  import { PEACReceiptClaims, SubjectProfileSnapshot } from '@peac/schema';
5
9
  import { type TelemetryHook } from './telemetry.js';
@@ -44,6 +48,9 @@ export interface VerifyOptions {
44
48
  /**
45
49
  * Verify a PEAC receipt JWS
46
50
  *
51
+ * Uses strict issuer-config discovery: peac-issuer.json -> jwks_uri -> JWKS.
52
+ * No fallback to peac.txt or direct JWKS endpoints.
53
+ *
47
54
  * @param optionsOrJws - Verify options or JWS compact serialization (for backwards compatibility)
48
55
  * @returns Verification result or failure
49
56
  */
@@ -1 +1 @@
1
- {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EACL,iBAAiB,EAEjB,sBAAsB,EAEvB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAkC,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AA8BpF;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,6BAA6B;IAC7B,EAAE,EAAE,IAAI,CAAC;IAET,qBAAqB;IACrB,MAAM,EAAE,iBAAiB,CAAC;IAE1B,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAE1C,0BAA0B;IAC1B,IAAI,CAAC,EAAE;QACL,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,0BAA0B;IAC1B,EAAE,EAAE,KAAK,CAAC;IAEV,mBAAmB;IACnB,MAAM,EAAE,MAAM,CAAC;IAEf,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAqGD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IAEnB,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAE1C,iDAAiD;IACjD,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GAAG,aAAa,GACnC,OAAO,CAAC,YAAY,GAAG,aAAa,CAAC,CAyHvC"}
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EACL,iBAAiB,EAEjB,sBAAsB,EAGvB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAkC,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAmBpF;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,6BAA6B;IAC7B,EAAE,EAAE,IAAI,CAAC;IAET,qBAAqB;IACrB,MAAM,EAAE,iBAAiB,CAAC;IAE1B,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAE1C,0BAA0B;IAC1B,IAAI,CAAC,EAAE;QACL,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,0BAA0B;IAC1B,EAAE,EAAE,KAAK,CAAC;IAEV,mBAAmB;IACnB,MAAM,EAAE,MAAM,CAAC;IAEf,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IAEnB,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAE1C,iDAAiD;IACjD,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GAAG,aAAa,GACnC,OAAO,CAAC,YAAY,GAAG,aAAa,CAAC,CA6IvC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peac/protocol",
3
- "version": "0.10.14",
3
+ "version": "0.11.1",
4
4
  "description": "PEAC protocol implementation - receipt issuance and verification",
5
5
  "main": "dist/index.cjs",
6
6
  "types": "dist/index.d.ts",
@@ -38,10 +38,10 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "uuidv7": "^0.6.3",
41
- "zod": "^3.22.4",
42
- "@peac/kernel": "0.10.14",
43
- "@peac/schema": "0.10.14",
44
- "@peac/crypto": "0.10.14"
41
+ "zod": "^4.3.6",
42
+ "@peac/schema": "0.11.1",
43
+ "@peac/kernel": "0.11.1",
44
+ "@peac/crypto": "0.11.1"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^22.19.11",