@xfuel/verify 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -12,13 +12,18 @@
12
12
  * See docs/specs/RECEIPT_V2_SEMANTICS.md for the verification algorithm.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.verifyIssuerJws = exports.resolvePinnedIssuerJwk = exports.canonicalSignedPayload = exports.BASE_SEPOLIA_RPC_URL = exports.ZK_VERIFIER_ADDRESS = exports.decodeJwsPayload = exports.receiptPayerClaimsFromEnvelope = exports.verifyPayerBinding = exports.SOLANA_USDC_MINT_DEVNET = exports.SOLANA_USDC_MINT_MAINNET = exports.SOLANA_RPC_URL = exports.fetchSolanaTransaction = exports.inferUsdcOutflowFromBalances = exports.extractUsdcTransfersFromTx = exports.isSolanaBase58Pubkey = exports.parseSolanaPaymentRef = exports.verifySolanaPayer = exports.BASE_RPC_URL = exports.ERC20_TRANSFER_TOPIC = exports.USDC_ADDRESSES = exports.fetchBaseTransactionReceipt = exports.sumUsdcTransfersFromPayer = exports.isEvmAddress = exports.parseBasePaymentRef = exports.verifyBasePayer = exports.computeInferenceBinding = exports.computePaymentCommitment = void 0;
15
+ exports.jwksHostAllowed = exports.isPinnedTrustedJwk = exports.jwkThumbprint = exports.KEY_UNTRUSTED = exports.DEFAULT_TRUSTED_JWKS_HOSTS = exports.DEFAULT_TRUSTED_ISSUER_KIDS = exports.verifyIssuerJws = exports.resolvePinnedIssuerJwk = exports.canonicalSignedPayload = exports.BASE_SEPOLIA_RPC_URL = exports.ZK_VERIFIER_ADDRESS = exports.decodeJwsPayload = exports.receiptPayerClaimsFromEnvelope = exports.verifyPayerBinding = exports.SOLANA_USDC_MINT_DEVNET = exports.SOLANA_USDC_MINT_MAINNET = exports.SOLANA_RPC_URL = exports.fetchSolanaTransaction = exports.inferUsdcOutflowFromBalances = exports.extractUsdcTransfersFromTx = exports.isSolanaBase58Pubkey = exports.parseSolanaPaymentRef = exports.verifySolanaPayer = exports.BASE_RPC_URL = exports.ERC20_TRANSFER_TOPIC = exports.USDC_ADDRESSES = exports.fetchBaseTransactionReceipt = exports.sumUsdcTransfersFromPayer = exports.isEvmAddress = exports.parseBasePaymentRef = exports.verifyBasePayer = exports.computeInferenceBinding = exports.computePaymentCommitment = void 0;
16
+ exports.canonicalPayloadVersion = canonicalPayloadVersion;
16
17
  exports.canonicalIssuerPayload = canonicalIssuerPayload;
18
+ exports.sumUsdcTransfersToPayee = sumUsdcTransfersToPayee;
19
+ exports.reconcileSettledTransfer = reconcileSettledTransfer;
17
20
  exports.verifyIssuerSignature = verifyIssuerSignature;
18
21
  exports.verifyIssuerSignatureWithJwks = verifyIssuerSignatureWithJwks;
19
22
  exports.verifyBinding = verifyBinding;
20
23
  exports.verifyNullifier = verifyNullifier;
21
24
  exports.hashCanonicalPayload = hashCanonicalPayload;
25
+ exports.diffOuterClaims = diffOuterClaims;
26
+ exports.fetchIssuerJwks = fetchIssuerJwks;
22
27
  exports.receiptPayerClaims = receiptPayerClaims;
23
28
  exports.verifyReceipt = verifyReceipt;
24
29
  const ethers_1 = require("ethers");
@@ -59,28 +64,28 @@ const ZK_VERIFIER_ABI = [
59
64
  'function usedNullifiers(bytes32) view returns (bool)',
60
65
  ];
61
66
  /**
62
- * Canonical, order-stable payload an issuer signature covers.
63
- * MUST match `canonicalSignedPayload` in services/gateway/src/receipt.js exactly.
64
- *
65
- * Signed fields (15 total):
66
- * 1. task_id
67
- * 2. payment.rail
68
- * 3. payment.ref
69
- * 4. payment.gross_amount
70
- * 5. payment.net_amount
71
- * 6. payment.fee_amount
72
- * 7. payment.protocol_fee_bps ?? payment.fee_bps
73
- * 8. payment.platform_fee
74
- * 9. payment.platform_fee_bps
75
- * 10. provider_cogs.actual
76
- * 11. route.model
77
- * 12. route.model_commitment.commitment
78
- * 13. route.provider
79
- * 14. output.hash
80
- * 15. binding.expected_commitment
67
+ * HMAC payload version.
68
+ * Versions <= 7 keep the historical net/fee field list.
69
+ * Version 8 signs settled_amount plus the internal accounting breakdown.
70
+ * Lockstep with `canonicalPayloadVersion` in services/gateway/src/receipt.js
71
+ * for the v8 list (the v7 list here is the historical 15-field public formula).
81
72
  */
82
- function canonicalIssuerPayload(receipt) {
83
- return JSON.stringify([
73
+ function canonicalPayloadVersion(receipt) {
74
+ const stamped = receipt.hmac_attestation?.payload_version
75
+ ?? receipt.signature?.payload_version;
76
+ if (stamped != null)
77
+ return Number(stamped);
78
+ const issuerV = receipt.issuer_signature?.payload_version;
79
+ if (issuerV != null)
80
+ return Number(issuerV);
81
+ const payment = receipt.payment;
82
+ if (payment?.accounting || (payment && Object.prototype.hasOwnProperty.call(payment, 'settled_amount'))) {
83
+ return 8;
84
+ }
85
+ return 7;
86
+ }
87
+ function canonicalFieldsV7(receipt) {
88
+ return [
84
89
  receipt.task_id ?? null,
85
90
  receipt.payment?.rail ?? null,
86
91
  receipt.payment?.ref ?? null,
@@ -96,7 +101,204 @@ function canonicalIssuerPayload(receipt) {
96
101
  receipt.route?.provider ?? null,
97
102
  receipt.output?.hash ?? null,
98
103
  receipt.binding?.expected_commitment ?? null,
99
- ]);
104
+ ];
105
+ }
106
+ function canonicalFieldsV8(receipt) {
107
+ const breakdown = receipt.payment?.accounting?.internal_breakdown;
108
+ const caller = receipt.caller_binding;
109
+ return [
110
+ receipt.task_id ?? null,
111
+ receipt.payment?.rail ?? null,
112
+ receipt.payment?.ref ?? null,
113
+ receipt.payment?.gross_amount ?? null,
114
+ receipt.payment?.settled_amount ?? null,
115
+ breakdown?.route_margin_bps ?? null,
116
+ breakdown?.route_margin_amount ?? null,
117
+ breakdown?.receipt_floor_amount ?? null,
118
+ breakdown?.provider_cogs_amount ?? null,
119
+ breakdown?.tier2_proof_amount ?? null,
120
+ receipt.provider_cogs?.actual ?? null,
121
+ receipt.route?.model ?? null,
122
+ receipt.route?.model_commitment?.commitment ?? null,
123
+ receipt.route?.provider ?? null,
124
+ receipt.output?.hash ?? null,
125
+ receipt.binding?.expected_commitment ?? null,
126
+ caller?.payer_wallet ?? null,
127
+ caller?.agent_pubkey ?? null,
128
+ caller?.api_key_hash ?? null,
129
+ ];
130
+ }
131
+ /**
132
+ * Canonical, order-stable payload an HMAC issuer signature covers.
133
+ * v8 matches `canonicalSignedPayload` in services/gateway/src/receipt.js.
134
+ * v7 and earlier stay on the historical 15-field list so old signatures verify.
135
+ */
136
+ function canonicalIssuerPayload(receipt) {
137
+ const fields = canonicalPayloadVersion(receipt) >= 8
138
+ ? canonicalFieldsV8(receipt)
139
+ : canonicalFieldsV7(receipt);
140
+ return JSON.stringify(fields);
141
+ }
142
+ function topicAddress(topic) {
143
+ const hex = topic.toLowerCase().replace(/^0x/, '');
144
+ return `0x${hex.slice(-40)}`;
145
+ }
146
+ /**
147
+ * Sum USDC Transfer values to `payee` in a transaction's logs.
148
+ * Non-Transfer logs are ignored. When `usdcAddress` is set, other contracts are ignored.
149
+ */
150
+ function sumUsdcTransfersToPayee(logs, payee, usdcAddress) {
151
+ if (!payee || !(0, base_payer_js_1.isEvmAddress)(payee))
152
+ return null;
153
+ const payeeLower = payee.toLowerCase();
154
+ const usdcLower = usdcAddress?.toLowerCase();
155
+ let total = 0n;
156
+ let found = false;
157
+ for (const log of logs || []) {
158
+ if (usdcLower && log.address?.toLowerCase() !== usdcLower)
159
+ continue;
160
+ const topics = log.topics || [];
161
+ if (topics[0]?.toLowerCase() !== base_payer_js_1.ERC20_TRANSFER_TOPIC)
162
+ continue;
163
+ if (topics.length < 3 || !topics[2])
164
+ continue;
165
+ if (topicAddress(topics[2]) !== payeeLower)
166
+ continue;
167
+ found = true;
168
+ total += BigInt(log.data || '0x0');
169
+ }
170
+ return found ? total : null;
171
+ }
172
+ /**
173
+ * Payment facts for on-chain reconciliation.
174
+ * A JWS is used only after `verifyIssuerSignatureWithJwks` accepts the key.
175
+ * An untrusted or invalid JWS is not decoded into the comparison, and the
176
+ * unsigned outer `payment` copy is not a fallback for those claims.
177
+ * Receipts with no JWS still use the outer payment (legacy envelopes).
178
+ */
179
+ function trustedPaymentForReconcile(receipt, options) {
180
+ const outerVersion = Number(receipt.issuer_signature?.payload_version ?? canonicalPayloadVersion(receipt));
181
+ const jws = receipt.issuer_signature?.jws;
182
+ if (!jws) {
183
+ return { ok: true, version: outerVersion, payment: receipt.payment || {} };
184
+ }
185
+ const verification = verifyIssuerSignatureWithJwks(receipt, options.jwks, {
186
+ trustedKids: options.trustedKids,
187
+ });
188
+ if (!verification.valid || verification.key_trusted === false) {
189
+ return {
190
+ ok: false,
191
+ version: outerVersion,
192
+ reason: verification.reason || jws_js_1.KEY_UNTRUSTED,
193
+ };
194
+ }
195
+ const claims = verification.payload;
196
+ const claimPayment = (claims?.payment && typeof claims.payment === 'object')
197
+ ? claims.payment
198
+ : null;
199
+ if (!claimPayment) {
200
+ return { ok: false, version: outerVersion, reason: 'no_signed_amount' };
201
+ }
202
+ const version = typeof claims?.payload_version === 'number'
203
+ ? claims.payload_version
204
+ : outerVersion;
205
+ return { ok: true, version: Number(version), payment: claimPayment };
206
+ }
207
+ /**
208
+ * Optional on-chain reconciliation. Given a tx receipt's logs, the signed
209
+ * amount the payee was supposed to receive must equal the USDC Transfer to payee.
210
+ *
211
+ * The amount and payee come from JWS claims only after a key-trust check
212
+ * (JWKS by kid, or an embedded key whose thumbprint is a pinned kid). An
213
+ * untrusted signature does not contribute those facts.
214
+ *
215
+ * v8 compares `settled_amount` (falling back to `gross_amount`): that is the
216
+ * on-chain transfer. v7 and earlier compare `net_amount`, which those receipts
217
+ * defined as "amount after fees". A single direct transfer of gross will not
218
+ * match a net that subtracts a fee that never moved on chain.
219
+ */
220
+ function reconcileSettledTransfer(receipt, logs, options = {}) {
221
+ const trusted = trustedPaymentForReconcile(receipt, options);
222
+ if (!trusted.ok) {
223
+ return {
224
+ checked: false,
225
+ matches: false,
226
+ payload_version: trusted.version,
227
+ signed_field: null,
228
+ signed_amount: null,
229
+ transfer_amount: null,
230
+ payee: null,
231
+ reason: trusted.reason,
232
+ };
233
+ }
234
+ const { version, payment } = trusted;
235
+ const payee = payment.payee ?? null;
236
+ let signed_field = null;
237
+ let signed_amount = null;
238
+ if (version >= 8) {
239
+ if (payment.settled_amount != null && payment.settled_amount !== '') {
240
+ signed_field = 'settled_amount';
241
+ signed_amount = String(payment.settled_amount);
242
+ }
243
+ else if (payment.gross_amount != null && payment.gross_amount !== '') {
244
+ signed_field = 'gross_amount';
245
+ signed_amount = String(payment.gross_amount);
246
+ }
247
+ }
248
+ else {
249
+ signed_field = 'net_amount';
250
+ signed_amount = payment.net_amount != null ? String(payment.net_amount) : null;
251
+ }
252
+ if (!payee || !(0, base_payer_js_1.isEvmAddress)(payee)) {
253
+ return {
254
+ checked: false,
255
+ matches: false,
256
+ payload_version: version,
257
+ signed_field,
258
+ signed_amount,
259
+ transfer_amount: null,
260
+ payee,
261
+ reason: 'no_payee',
262
+ };
263
+ }
264
+ if (signed_amount == null) {
265
+ return {
266
+ checked: false,
267
+ matches: false,
268
+ payload_version: version,
269
+ signed_field,
270
+ signed_amount,
271
+ transfer_amount: null,
272
+ payee,
273
+ reason: 'no_signed_amount',
274
+ };
275
+ }
276
+ const transfer = sumUsdcTransfersToPayee(logs, payee, options.usdcAddress);
277
+ if (transfer == null) {
278
+ return {
279
+ checked: true,
280
+ matches: false,
281
+ payload_version: version,
282
+ signed_field,
283
+ signed_amount,
284
+ transfer_amount: null,
285
+ payee,
286
+ reason: 'no_transfer_to_payee',
287
+ };
288
+ }
289
+ const matches = BigInt(signed_amount) === transfer;
290
+ return {
291
+ checked: true,
292
+ matches,
293
+ payload_version: version,
294
+ signed_field,
295
+ signed_amount,
296
+ transfer_amount: transfer.toString(),
297
+ payee,
298
+ reason: matches
299
+ ? undefined
300
+ : `amount_mismatch: signed ${signed_field} ${signed_amount} !== transfer ${transfer.toString()}`,
301
+ };
100
302
  }
101
303
  /**
102
304
  * Verify a receipt's ES256 issuer signature against a JWK (public key).
@@ -146,59 +348,122 @@ function verifyIssuerSignature(receipt, jwk) {
146
348
  * @param receipt - Receipt JSON with issuer_signature
147
349
  * @param jwks - JWKS with keys array
148
350
  */
149
- function verifyIssuerSignatureWithJwks(receipt, jwks) {
351
+ function jwksCandidates(jwks, kid) {
352
+ const keys = jwks?.keys || [];
353
+ const es256 = keys.filter((k) => (0, jws_js_1.isEs256PublicJwk)(k) && (k.alg == null || k.alg === 'ES256'));
354
+ if (!kid)
355
+ return es256;
356
+ return es256.filter((k) => k.kid === kid);
357
+ }
358
+ /**
359
+ * Verify an issuer signature against trusted keys only.
360
+ *
361
+ * Trust, in order:
362
+ * 1. A JWKS entry matched by `kid` (supplied file or fetched issuer JWKS).
363
+ * 2. An embedded `issuer_jwk` whose RFC 7638 thumbprint equals a pinned trusted kid.
364
+ *
365
+ * An embedded key that merely verifies the bytes is `key untrusted`.
366
+ */
367
+ function verifyIssuerSignatureWithJwks(receipt, jwks, options = {}) {
150
368
  const sig = receipt.issuer_signature;
151
369
  if (!sig) {
152
- return { checked: false, valid: false, reason: 'no_issuer_signature' };
153
- }
154
- const pinned = (0, jws_js_1.resolvePinnedIssuerJwk)(receipt);
155
- if (pinned && sig.jws) {
156
- const jwsResult = (0, jws_js_1.verifyIssuerJws)(sig.jws, pinned);
157
- return {
158
- checked: true,
159
- valid: jwsResult.valid,
160
- kid: pinned.kid || sig.kid,
161
- reason: jwsResult.reason,
162
- };
370
+ return { checked: false, valid: false, key_trusted: false, reason: 'no_issuer_signature' };
163
371
  }
372
+ const trustedKids = options.trustedKids ?? jws_js_1.DEFAULT_TRUSTED_ISSUER_KIDS;
373
+ const header = sig.jws ? (0, jws_js_1.readJwsHeader)(sig.jws) : null;
374
+ const kid = header?.kid || sig.kid;
375
+ const embedded = (0, jws_js_1.resolvePinnedIssuerJwk)(receipt);
376
+ const pinned = !!(embedded && (0, jws_js_1.isPinnedTrustedJwk)(embedded, trustedKids));
164
377
  if (sig.jws) {
165
- if (!jwks || !Array.isArray(jwks.keys) || jwks.keys.length === 0) {
166
- return { checked: false, valid: false, reason: 'empty_jwks' };
167
- }
168
- const candidates = sig.kid
169
- ? jwks.keys.filter(k => k.kid === sig.kid && k.alg === 'ES256')
170
- : jwks.keys.filter(k => k.alg === 'ES256');
171
- if (candidates.length === 0) {
172
- return { checked: false, valid: false, reason: 'no_matching_key' };
173
- }
174
- for (const jwk of candidates) {
378
+ const fromJwks = jwksCandidates(jwks, kid);
379
+ for (const jwk of fromJwks) {
175
380
  const jwsResult = (0, jws_js_1.verifyIssuerJws)(sig.jws, jwk);
176
381
  if (jwsResult.valid) {
177
- return { checked: true, valid: true, kid: jwk.kid || sig.kid };
382
+ return {
383
+ checked: true,
384
+ valid: true,
385
+ key_trusted: true,
386
+ trust: 'jwks',
387
+ kid: jwk.kid || kid,
388
+ payload: jwsResult.payload,
389
+ };
390
+ }
391
+ }
392
+ if (pinned && embedded) {
393
+ const pinResult = (0, jws_js_1.verifyIssuerJws)(sig.jws, embedded);
394
+ if (pinResult.valid) {
395
+ return {
396
+ checked: true,
397
+ valid: true,
398
+ key_trusted: true,
399
+ trust: 'pinned_kid',
400
+ kid: embedded.kid || (0, jws_js_1.jwkThumbprint)(embedded),
401
+ payload: pinResult.payload,
402
+ };
178
403
  }
404
+ return {
405
+ checked: true,
406
+ valid: false,
407
+ key_trusted: true,
408
+ kid: kid || embedded.kid,
409
+ reason: pinResult.reason || 'signature_invalid',
410
+ };
411
+ }
412
+ if (embedded && (0, jws_js_1.verifyIssuerJws)(sig.jws, embedded).valid) {
413
+ return {
414
+ checked: true,
415
+ valid: false,
416
+ key_trusted: false,
417
+ kid: embedded.kid || kid,
418
+ reason: jws_js_1.KEY_UNTRUSTED,
419
+ };
420
+ }
421
+ if (fromJwks.length === 0 && !pinned) {
422
+ return {
423
+ checked: true,
424
+ valid: false,
425
+ key_trusted: false,
426
+ kid,
427
+ reason: jws_js_1.KEY_UNTRUSTED,
428
+ };
179
429
  }
180
- return { checked: true, valid: false, reason: 'signature_invalid' };
430
+ return {
431
+ checked: true,
432
+ valid: false,
433
+ key_trusted: fromJwks.length > 0 || pinned,
434
+ kid,
435
+ reason: 'signature_invalid',
436
+ };
181
437
  }
182
438
  if (!sig.value) {
183
- return { checked: false, valid: false, reason: 'no_issuer_signature' };
439
+ return { checked: false, valid: false, key_trusted: false, reason: 'no_issuer_signature' };
440
+ }
441
+ if (!jwks || !Array.isArray(jwks.keys)) {
442
+ return {
443
+ checked: false,
444
+ valid: false,
445
+ key_trusted: false,
446
+ kid: sig.kid,
447
+ reason: 'JWKS not provided — pass jwks option to verify issuer signature',
448
+ };
184
449
  }
185
- if (!jwks || !Array.isArray(jwks.keys) || jwks.keys.length === 0) {
186
- return { checked: false, valid: false, reason: 'empty_jwks' };
450
+ if (jwks.keys.length === 0) {
451
+ return { checked: false, valid: false, key_trusted: false, kid: sig.kid, reason: 'empty_jwks' };
187
452
  }
188
453
  // Find matching key by kid, or try all ES256 keys if no kid on signature
189
454
  const candidates = sig.kid
190
455
  ? jwks.keys.filter(k => k.kid === sig.kid && k.alg === 'ES256')
191
456
  : jwks.keys.filter(k => k.alg === 'ES256');
192
457
  if (candidates.length === 0) {
193
- return { checked: false, valid: false, reason: 'no_matching_key' };
458
+ return { checked: false, valid: false, key_trusted: false, kid: sig.kid, reason: 'no_matching_key' };
194
459
  }
195
460
  for (const jwk of candidates) {
196
461
  const result = verifyIssuerSignature(receipt, jwk);
197
462
  if (result.valid) {
198
- return result;
463
+ return { ...result, key_trusted: true, trust: 'jwks', kid: jwk.kid || sig.kid };
199
464
  }
200
465
  }
201
- return { checked: true, valid: false, reason: 'signature_invalid' };
466
+ return { checked: true, valid: false, key_trusted: true, kid: sig.kid, reason: 'signature_invalid' };
202
467
  }
203
468
  /**
204
469
  * Verify a receipt's payment binding locally (no network required).
@@ -206,8 +471,18 @@ function verifyIssuerSignatureWithJwks(receipt, jwks) {
206
471
  * This recomputes the commitment from the receipt's fields and compares
207
472
  * it to the stored `binding.expected_commitment`.
208
473
  */
474
+ function noCommitmentReason(rail) {
475
+ if (rail === 'unmetered' || rail === 'tfuel') {
476
+ return 'No binding present on receipt (unmetered or TFUEL rail)';
477
+ }
478
+ if (rail === 'usdc' || rail === 'reported') {
479
+ return 'No payment-binding commitment (expected_commitment is null)';
480
+ }
481
+ return 'No binding present on receipt (may be unmetered or TFUEL rail)';
482
+ }
209
483
  function verifyBinding(receipt) {
210
484
  const binding = receipt.binding;
485
+ const railHint = binding?.rail || receipt.payment?.rail || null;
211
486
  if (!binding) {
212
487
  return {
213
488
  verified: false,
@@ -215,7 +490,17 @@ function verifyBinding(receipt) {
215
490
  recomputed: null,
216
491
  matches: false,
217
492
  covers: [],
218
- reason: 'No binding present on receipt (may be unmetered or TFUEL rail)',
493
+ reason: noCommitmentReason(railHint),
494
+ };
495
+ }
496
+ if (binding.expected_commitment == null || binding.expected_commitment === '') {
497
+ return {
498
+ verified: false,
499
+ expected: null,
500
+ recomputed: null,
501
+ matches: false,
502
+ covers: binding.covers || [],
503
+ reason: noCommitmentReason(railHint),
219
504
  };
220
505
  }
221
506
  const paymentRef = receipt.payment?.ref ?? null;
@@ -328,89 +613,341 @@ exports.canonicalSignedPayload = canonicalIssuerPayload;
328
613
  function hashCanonicalPayload(receipt) {
329
614
  return (0, ethers_1.keccak256)((0, ethers_1.toUtf8Bytes)((0, exports.canonicalSignedPayload)(receipt)));
330
615
  }
616
+ function claimString(value) {
617
+ if (value == null || value === '')
618
+ return null;
619
+ return String(value);
620
+ }
621
+ function asRecord(value) {
622
+ if (!value || typeof value !== 'object' || Array.isArray(value))
623
+ return null;
624
+ return value;
625
+ }
626
+ function normFact(value, address = false) {
627
+ const text = claimString(value);
628
+ if (text == null)
629
+ return null;
630
+ return address ? text.toLowerCase() : text;
631
+ }
331
632
  /**
332
- * Full receipt verification — binding + issuer signature + optional nullifier check.
633
+ * Fields a verifier may quote. Compared only when both the unsigned outer
634
+ * copy and the JWS payload carry a value.
635
+ */
636
+ const CLAIM_COMPARE = [
637
+ { field: 'task_id', outer: (r) => r.task_id, signed: (c) => c.task_id },
638
+ { field: 'payment.rail', outer: (r) => r.payment?.rail, signed: (c) => asRecord(c.payment)?.rail },
639
+ { field: 'payment.ref', outer: (r) => r.payment?.ref, signed: (c) => asRecord(c.payment)?.ref },
640
+ { field: 'payment.gross_amount', outer: (r) => r.payment?.gross_amount, signed: (c) => asRecord(c.payment)?.gross_amount },
641
+ { field: 'payment.settled_amount', outer: (r) => r.payment?.settled_amount, signed: (c) => asRecord(c.payment)?.settled_amount },
642
+ { field: 'payment.net_amount', outer: (r) => r.payment?.net_amount, signed: (c) => asRecord(c.payment)?.net_amount },
643
+ { field: 'payment.asset', outer: (r) => r.payment?.asset, signed: (c) => asRecord(c.payment)?.asset, address: true },
644
+ { field: 'payment.payee', outer: (r) => r.payment?.payee, signed: (c) => asRecord(c.payment)?.payee, address: true },
645
+ { field: 'caller_binding.payer_wallet', outer: (r) => r.caller_binding?.payer_wallet, signed: (c) => asRecord(c.caller_binding)?.payer_wallet, address: true },
646
+ { field: 'caller_binding.agent_pubkey', outer: (r) => r.caller_binding?.agent_pubkey, signed: (c) => asRecord(c.caller_binding)?.agent_pubkey },
647
+ { field: 'caller_binding.api_key_hash', outer: (r) => r.caller_binding?.api_key_hash, signed: (c) => asRecord(c.caller_binding)?.api_key_hash },
648
+ { field: 'route.model', outer: (r) => r.route?.model, signed: (c) => asRecord(c.route)?.model },
649
+ { field: 'route.provider', outer: (r) => r.route?.provider, signed: (c) => asRecord(c.route)?.provider },
650
+ { field: 'output.hash', outer: (r) => r.output?.hash, signed: (c) => asRecord(c.output)?.hash },
651
+ { field: 'binding.expected_commitment', outer: (r) => r.binding?.expected_commitment, signed: (c) => asRecord(c.binding)?.expected_commitment },
652
+ { field: 'provider_cogs.actual', outer: (r) => r.provider_cogs?.actual, signed: (c) => asRecord(c.provider_cogs)?.actual },
653
+ ];
654
+ /** Flag unsigned outer copies that disagree with the JWS payload. */
655
+ function diffOuterClaims(receipt, claims) {
656
+ if (!claims)
657
+ return [];
658
+ const mismatches = [];
659
+ for (const spec of CLAIM_COMPARE) {
660
+ const outer = normFact(spec.outer(receipt), spec.address);
661
+ const signed = normFact(spec.signed(claims), spec.address);
662
+ if (outer == null || signed == null || outer === signed)
663
+ continue;
664
+ mismatches.push({
665
+ field: spec.field,
666
+ outer: claimString(spec.outer(receipt)),
667
+ signed: claimString(spec.signed(claims)),
668
+ });
669
+ }
670
+ return mismatches;
671
+ }
672
+ function receiptViewFromClaims(receipt, claims) {
673
+ const payment = asRecord(claims.payment);
674
+ const binding = asRecord(claims.binding);
675
+ const route = asRecord(claims.route);
676
+ const output = asRecord(claims.output);
677
+ const caller = asRecord(claims.caller_binding);
678
+ const modelCommitment = claimString(route?.model_commitment);
679
+ return {
680
+ ...receipt,
681
+ task_id: claimString(claims.task_id) || receipt.task_id,
682
+ payment: {
683
+ rail: claimString(payment?.rail) ?? undefined,
684
+ ref: claimString(payment?.ref),
685
+ gross_amount: claimString(payment?.gross_amount),
686
+ settled_amount: claimString(payment?.settled_amount),
687
+ net_amount: claimString(payment?.net_amount),
688
+ asset: claimString(payment?.asset),
689
+ payee: claimString(payment?.payee),
690
+ fee_amount: claimString(payment?.fee_amount),
691
+ protocol_fee_bps: typeof payment?.protocol_fee_bps === 'number' ? payment.protocol_fee_bps : null,
692
+ platform_fee: claimString(payment?.platform_fee),
693
+ platform_fee_bps: typeof payment?.platform_fee_bps === 'number' ? payment.platform_fee_bps : null,
694
+ },
695
+ route: {
696
+ model: claimString(route?.model) ?? undefined,
697
+ provider: claimString(route?.provider) ?? undefined,
698
+ model_commitment: modelCommitment ? { commitment: modelCommitment } : null,
699
+ },
700
+ output: output?.hash ? { hash: claimString(output.hash) ?? undefined } : null,
701
+ caller_binding: caller
702
+ ? {
703
+ payer_wallet: claimString(caller.payer_wallet),
704
+ agent_pubkey: claimString(caller.agent_pubkey),
705
+ api_key_hash: claimString(caller.api_key_hash),
706
+ }
707
+ : null,
708
+ provider_cogs: asRecord(claims.provider_cogs)?.actual != null
709
+ ? { actual: claimString(asRecord(claims.provider_cogs)?.actual) ?? undefined }
710
+ : receipt.provider_cogs,
711
+ binding: {
712
+ expected_commitment: binding && 'expected_commitment' in binding
713
+ ? (claimString(binding.expected_commitment) ?? undefined)
714
+ : undefined,
715
+ amount: claimString(binding?.amount)
716
+ || claimString(payment?.net_amount)
717
+ || claimString(payment?.gross_amount)
718
+ || '0',
719
+ rail: claimString(binding?.rail) || claimString(payment?.rail) || undefined,
720
+ covers: Array.isArray(binding?.covers) ? binding.covers : ['payment', 'settlement'],
721
+ model_commitment: claimString(binding?.model_commitment) || modelCommitment,
722
+ output_hash: claimString(binding?.output_hash) || claimString(output?.hash),
723
+ },
724
+ };
725
+ }
726
+ function payerClaimsFromPayload(payload) {
727
+ const payment = asRecord(payload.payment);
728
+ const caller = asRecord(payload.caller_binding);
729
+ return {
730
+ payment: {
731
+ ref: claimString(payment?.ref),
732
+ gross_amount: claimString(payment?.gross_amount),
733
+ payee: claimString(payment?.payee),
734
+ asset: claimString(payment?.asset),
735
+ rail: claimString(payment?.rail),
736
+ },
737
+ caller_binding: caller ? { payer_wallet: claimString(caller.payer_wallet) } : null,
738
+ };
739
+ }
740
+ function factsFromClaims(claims) {
741
+ if (!claims) {
742
+ return { hub: null, model: null, amount_usdc: null, tx: null, output_hash: null };
743
+ }
744
+ const payment = asRecord(claims.payment);
745
+ const route = asRecord(claims.route);
746
+ const output = asRecord(claims.output);
747
+ return {
748
+ hub: claimString(route?.provider),
749
+ model: claimString(route?.model),
750
+ amount_usdc: claimString(payment?.gross_amount),
751
+ tx: claimString(payment?.ref),
752
+ output_hash: claimString(output?.hash),
753
+ };
754
+ }
755
+ function issuerJwksUri(receipt) {
756
+ const fromVerification = receipt.verification?.jwks_uri;
757
+ if (typeof fromVerification === 'string' && fromVerification.startsWith('https://'))
758
+ return fromVerification;
759
+ const jws = receipt.issuer_signature?.jws;
760
+ const header = jws ? (0, jws_js_1.readJwsHeader)(jws) : null;
761
+ if (header?.jku && header.jku.startsWith('https://'))
762
+ return header.jku;
763
+ return null;
764
+ }
765
+ function httpsUrl(uri) {
766
+ try {
767
+ return new URL(uri).protocol === 'https:';
768
+ }
769
+ catch {
770
+ return false;
771
+ }
772
+ }
773
+ function mergeJwks(primary, extra) {
774
+ const keys = [...(primary?.keys || []), ...(extra?.keys || [])];
775
+ if (keys.length === 0)
776
+ return undefined;
777
+ const seen = new Set();
778
+ const deduped = [];
779
+ for (const key of keys) {
780
+ const id = `${key.kid || ''}:${key.x || ''}:${key.y || ''}`;
781
+ if (seen.has(id))
782
+ continue;
783
+ seen.add(id);
784
+ deduped.push(key);
785
+ }
786
+ return { keys: deduped };
787
+ }
788
+ /** Fetch a JWKS document. Caller decides whether the URL is a trust root. */
789
+ async function fetchIssuerJwks(uri, fetchImpl = globalThis.fetch) {
790
+ if (!httpsUrl(uri))
791
+ throw new Error('jwks_uri must be https');
792
+ const res = await fetchImpl(uri);
793
+ if (!res.ok)
794
+ throw new Error(`jwks_fetch_failed: ${res.status}`);
795
+ const body = await res.json();
796
+ if (!body || !Array.isArray(body.keys))
797
+ throw new Error('jwks_malformed');
798
+ return body;
799
+ }
800
+ /**
801
+ * Full receipt verification — trusted issuer signature, signed-claim facts,
802
+ * optional on-chain payer and nullifier checks.
333
803
  *
334
- * @param receipt The Chit402 receipt JSON
335
- * @param options.jwks JWKS for issuer signature verification (no network fetch)
336
- * @param options.checkNullifier Whether to verify nullifier on-chain (requires network)
337
- * @param options.rpcUrl RPC URL for on-chain checks
804
+ * Facts (amount, payer, payee, asset, tx, model) are taken only from a JWS
805
+ * that verified under a trusted key. The embedded `issuer_jwk` is not trusted
806
+ * unless its RFC 7638 thumbprint is a pinned kid, or the same key is in a JWKS.
338
807
  */
339
- /** Extract JWS claims for payer binding when present. */
808
+ /** Extract JWS claims for payer binding. Prefers the JWS payload over the outer copy. */
340
809
  function receiptPayerClaims(receipt) {
341
810
  return (0, payer_js_1.receiptPayerClaimsFromEnvelope)(receipt);
342
811
  }
343
812
  async function verifyReceipt(receipt, options = {}) {
344
813
  const errors = [];
345
- // Verify binding locally
346
- const binding = verifyBinding(receipt);
347
- if (!binding.matches && binding.expected) {
348
- errors.push('Payment binding mismatch');
349
- }
350
- // Verify issuer signature — pinned JWK in receipt, optional JWKS file, or legacy detached sig
351
- let issuer_signature;
352
- const pinned = (0, jws_js_1.resolvePinnedIssuerJwk)(receipt);
353
- if (pinned && receipt.issuer_signature?.jws) {
354
- const jwsResult = (0, jws_js_1.verifyIssuerJws)(receipt.issuer_signature.jws, pinned);
355
- issuer_signature = {
356
- checked: true,
357
- valid: jwsResult.valid,
358
- kid: pinned.kid || receipt.issuer_signature.kid,
359
- reason: jwsResult.reason,
360
- };
361
- if (!jwsResult.valid) {
362
- errors.push(`Issuer signature invalid: ${jwsResult.reason}`);
814
+ const trustedKids = options.trustedKids ?? jws_js_1.DEFAULT_TRUSTED_ISSUER_KIDS;
815
+ const trustedHosts = options.trustedJwksHosts ?? jws_js_1.DEFAULT_TRUSTED_JWKS_HOSTS;
816
+ let jwks = options.jwks;
817
+ if (options.jwksUri || options.fetchJwks) {
818
+ const uri = options.jwksUri || issuerJwksUri(receipt);
819
+ const allowed = !!uri && (options.jwksUri ? httpsUrl(uri) : (0, jws_js_1.jwksHostAllowed)(uri, trustedHosts));
820
+ if (uri && allowed) {
821
+ try {
822
+ const fetched = await fetchIssuerJwks(uri, options.fetchImpl);
823
+ jwks = mergeJwks(jwks, fetched);
824
+ }
825
+ catch (err) {
826
+ errors.push(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
827
+ }
828
+ }
829
+ else if (options.jwksUri) {
830
+ errors.push('JWKS URI rejected — only https URLs are fetched');
831
+ }
832
+ else if (options.fetchJwks && uri) {
833
+ errors.push(`JWKS host untrusted: ${uri}`);
363
834
  }
364
835
  }
365
- else if (options.jwks) {
366
- issuer_signature = verifyIssuerSignatureWithJwks(receipt, options.jwks);
836
+ let issuer_signature;
837
+ if (receipt.issuer_signature?.jws || receipt.issuer_signature?.value) {
838
+ issuer_signature = verifyIssuerSignatureWithJwks(receipt, jwks, { trustedKids });
367
839
  if (issuer_signature.checked && !issuer_signature.valid) {
368
- errors.push(`Issuer signature invalid: ${issuer_signature.reason}`);
840
+ errors.push(issuer_signature.reason === jws_js_1.KEY_UNTRUSTED
841
+ ? jws_js_1.KEY_UNTRUSTED
842
+ : `Issuer signature invalid: ${issuer_signature.reason}`);
369
843
  }
370
844
  }
371
845
  else if (receipt.issuer_signature) {
372
846
  issuer_signature = {
373
847
  checked: false,
374
848
  valid: false,
849
+ key_trusted: false,
375
850
  kid: receipt.issuer_signature.kid,
376
- reason: receipt.issuer_signature.jws
377
- ? 'No pinned issuer_jwk and JWKS not provided — pass jwks option or use a receipt with issuer_signature.issuer_jwk'
378
- : 'JWKS not provided — pass jwks option to verify issuer signature',
851
+ reason: 'JWKS not provided — pass jwks option to verify issuer signature',
379
852
  };
380
853
  }
381
854
  else {
382
855
  issuer_signature = {
383
856
  checked: false,
384
857
  valid: false,
858
+ key_trusted: false,
385
859
  reason: 'No issuer signature present on receipt',
386
860
  };
387
861
  }
388
- // Verify payer on-chain when requested (Base USDC or Solana USDC)
862
+ const decoded = receipt.issuer_signature?.jws
863
+ ? (0, payer_js_1.decodeJwsPayload)(receipt.issuer_signature.jws)
864
+ : null;
865
+ const claim_mismatches = diffOuterClaims(receipt, decoded);
866
+ for (const mismatch of claim_mismatches) {
867
+ errors.push(`outer/signed mismatch: ${mismatch.field} (outer ${mismatch.outer}, signed ${mismatch.signed})`);
868
+ }
869
+ const verifiedClaims = issuer_signature.valid ? issuer_signature.payload : undefined;
870
+ let binding;
871
+ if (receipt.issuer_signature?.jws) {
872
+ if (verifiedClaims) {
873
+ binding = verifyBinding(receiptViewFromClaims(receipt, verifiedClaims));
874
+ }
875
+ else {
876
+ binding = {
877
+ verified: false,
878
+ expected: null,
879
+ recomputed: null,
880
+ matches: false,
881
+ covers: [],
882
+ reason: issuer_signature.reason === jws_js_1.KEY_UNTRUSTED
883
+ ? 'Binding not checked — issuer key untrusted'
884
+ : 'Binding not checked — issuer signature not verified',
885
+ };
886
+ }
887
+ }
888
+ else {
889
+ binding = verifyBinding(receipt);
890
+ }
891
+ if (!binding.matches && binding.expected) {
892
+ errors.push('Payment binding mismatch');
893
+ }
894
+ const signedPayerClaims = verifiedClaims ? payerClaimsFromPayload(verifiedClaims) : null;
389
895
  let payer;
390
896
  if (options.checkPayer) {
391
- const payerResult = await (0, payer_js_1.verifyPayerBinding)(receiptPayerClaims(receipt), {
392
- rpcUrl: options.rpcUrl,
393
- solanaRpcUrl: options.solanaRpcUrl,
394
- fetchSolanaTransaction: options.fetchSolanaTransaction,
395
- fetchBaseReceipt: options.fetchBaseReceipt,
396
- });
897
+ if (!signedPayerClaims) {
898
+ payer = {
899
+ checked: false,
900
+ valid: false,
901
+ reason: 'Payer facts come from verified signed claims — issuer signature is not trusted',
902
+ };
903
+ }
904
+ else {
905
+ const payerResult = await (0, payer_js_1.verifyPayerBinding)(signedPayerClaims, {
906
+ rpcUrl: options.rpcUrl,
907
+ solanaRpcUrl: options.solanaRpcUrl,
908
+ fetchSolanaTransaction: options.fetchSolanaTransaction,
909
+ fetchBaseReceipt: options.fetchBaseReceipt,
910
+ });
911
+ payer = {
912
+ checked: payerResult.checked,
913
+ valid: payerResult.valid,
914
+ rail: payerResult.rail,
915
+ reason: payerResult.reason,
916
+ payer_wallet: payerResult.payerWallet ?? signedPayerClaims.caller_binding?.payer_wallet ?? null,
917
+ payee: signedPayerClaims.payment?.payee ?? null,
918
+ asset: signedPayerClaims.payment?.asset ?? null,
919
+ amount: payerResult.expectedAmount ?? signedPayerClaims.payment?.gross_amount ?? null,
920
+ };
921
+ if (payerResult.checked && !payerResult.valid) {
922
+ errors.push(`Payer binding mismatch: ${payerResult.reason}`);
923
+ }
924
+ }
925
+ }
926
+ else if (signedPayerClaims?.caller_binding?.payer_wallet && signedPayerClaims.payment?.ref) {
397
927
  payer = {
398
- checked: payerResult.checked,
399
- valid: payerResult.valid,
400
- rail: payerResult.rail,
401
- reason: payerResult.reason,
928
+ checked: false,
929
+ valid: false,
930
+ payer_wallet: signedPayerClaims.caller_binding.payer_wallet,
931
+ payee: signedPayerClaims.payment.payee ?? null,
932
+ asset: signedPayerClaims.payment.asset ?? null,
933
+ amount: signedPayerClaims.payment.gross_amount ?? null,
934
+ reason: 'On-chain payer check not requested — pass checkPayer: true',
402
935
  };
403
- if (payerResult.checked && !payerResult.valid) {
404
- errors.push(`Payer binding mismatch: ${payerResult.reason}`);
405
- }
406
936
  }
407
- else if (receipt.caller_binding?.payer_wallet && receipt.payment?.ref) {
937
+ else if (!receipt.issuer_signature?.jws && receipt.caller_binding?.payer_wallet && receipt.payment?.ref) {
408
938
  payer = {
409
939
  checked: false,
410
940
  valid: false,
411
941
  reason: 'On-chain payer check not requested — pass checkPayer: true',
412
942
  };
413
943
  }
944
+ else if (receipt.issuer_signature?.jws && !issuer_signature.valid) {
945
+ payer = {
946
+ checked: false,
947
+ valid: false,
948
+ reason: 'Payer facts come from verified signed claims — issuer signature is not trusted',
949
+ };
950
+ }
414
951
  else {
415
952
  payer = {
416
953
  checked: false,
@@ -418,7 +955,6 @@ async function verifyReceipt(receipt, options = {}) {
418
955
  reason: 'No payer_wallet or payment.ref to verify',
419
956
  };
420
957
  }
421
- // Verify nullifier on-chain if requested
422
958
  let nullifier;
423
959
  if (options.checkNullifier && receipt.proof?.nullifier) {
424
960
  nullifier = await verifyNullifier(receipt, {
@@ -439,52 +975,39 @@ async function verifyReceipt(receipt, options = {}) {
439
975
  : 'On-chain check not requested',
440
976
  };
441
977
  }
442
- // Extract frozen fields
443
- const hub = receipt.route?.provider ?? null;
444
- const model = receipt.route?.model ?? null;
445
- const amount_usdc = receipt.payment?.gross_amount ?? null;
446
- const tx = receipt.payment?.ref ?? null;
447
- const output_hash = receipt.output?.hash ?? null;
448
- // Determine overall status with strict signature semantics:
449
- // - If JWKS provided and signature invalid/kid mismatch/no matching key → 'failed'
450
- // - If receipt has issuer_signature but no JWKS provided → 'partial' (cannot verify)
451
- // - Unsigned receipts can be 'partial' or 'verified' based on binding
452
- let overall;
453
- // Check for signature verification failure (JWKS provided but signature invalid)
454
- const jwksProvided = !!options.jwks;
978
+ const facts = factsFromClaims(verifiedClaims);
455
979
  const hasIssuerSig = !!receipt.issuer_signature;
456
- const hasPinnedJwk = !!pinned && !!receipt.issuer_signature?.jws;
457
- const issuerSigInvalid = (jwksProvided || hasPinnedJwk) && hasIssuerSig && !issuer_signature.valid;
458
- // Binding failure
459
- const bindingFailed = binding.expected && !binding.matches;
460
- const payerFailed = options.checkPayer && payer.checked && !payer.valid;
461
- if (issuerSigInvalid || bindingFailed || payerFailed) {
462
- // JWKS provided but signature invalid, OR binding mismatch → failed
980
+ const jwksWasSupplied = !!(options.jwks || options.jwksUri || (options.fetchJwks && jwks));
981
+ const signatureFailed = hasIssuerSig && !issuer_signature.valid && (!!issuer_signature.checked
982
+ || jwksWasSupplied
983
+ || issuer_signature.reason === jws_js_1.KEY_UNTRUSTED
984
+ || issuer_signature.reason === 'no_matching_key'
985
+ || issuer_signature.reason === 'empty_jwks');
986
+ const mismatchFailed = claim_mismatches.length > 0;
987
+ const bindingFailed = !!(binding.expected && !binding.matches);
988
+ const payerFailed = !!(options.checkPayer && payer.checked && !payer.valid);
989
+ const nullifierFailed = !!(options.checkNullifier
990
+ && receipt.proof?.nullifier
991
+ && nullifier.anchored === false);
992
+ const signatureUnchecked = hasIssuerSig && !issuer_signature.checked;
993
+ let overall;
994
+ if (signatureFailed || mismatchFailed || bindingFailed || payerFailed || nullifierFailed) {
463
995
  overall = 'failed';
464
996
  }
465
- else if (hasIssuerSig && !jwksProvided && !hasPinnedJwk) {
466
- // Receipt has signature but no offline key material → partial
997
+ else if (signatureUnchecked) {
467
998
  overall = 'partial';
468
999
  }
469
- else if (binding.matches
470
- && (!options.checkPayer || payer.valid)
471
- && (!options.checkNullifier || nullifier.anchored)) {
472
- // Binding matches, signature OK (or no signature), nullifier OK → verified
473
- overall = 'verified';
474
- }
475
- else if (binding.matches
476
- || issuer_signature.valid
477
- || (options.checkPayer && payer.valid)
478
- || (nullifier.verified && nullifier.anchored)) {
479
- // At least one check passed → partial
1000
+ else if (!binding.expected && !hasIssuerSig && !options.checkPayer) {
480
1001
  overall = 'partial';
481
1002
  }
482
- else if (!binding.expected && !receipt.proof?.nullifier && !hasIssuerSig) {
483
- // No binding/sig/nullifier to verify (unmetered/demo) → partial
484
- overall = 'partial';
1003
+ else if ((binding.matches || !binding.expected)
1004
+ && (!options.checkPayer || payer.valid)
1005
+ && (!options.checkNullifier || !receipt.proof?.nullifier || nullifier.anchored === true)
1006
+ && (issuer_signature.valid || !hasIssuerSig)) {
1007
+ overall = 'verified';
485
1008
  }
486
1009
  else {
487
- overall = 'failed';
1010
+ overall = 'partial';
488
1011
  }
489
1012
  return {
490
1013
  receipt_id: receipt.task_id,
@@ -492,11 +1015,12 @@ async function verifyReceipt(receipt, options = {}) {
492
1015
  issuer_signature,
493
1016
  payer,
494
1017
  nullifier,
495
- output_hash,
496
- hub,
497
- model,
498
- amount_usdc,
499
- tx,
1018
+ output_hash: facts.output_hash,
1019
+ hub: facts.hub,
1020
+ model: facts.model,
1021
+ amount_usdc: facts.amount_usdc,
1022
+ tx: facts.tx,
1023
+ claim_mismatches,
500
1024
  overall,
501
1025
  errors,
502
1026
  };
@@ -504,6 +1028,12 @@ async function verifyReceipt(receipt, options = {}) {
504
1028
  var jws_js_2 = require("./jws.js");
505
1029
  Object.defineProperty(exports, "resolvePinnedIssuerJwk", { enumerable: true, get: function () { return jws_js_2.resolvePinnedIssuerJwk; } });
506
1030
  Object.defineProperty(exports, "verifyIssuerJws", { enumerable: true, get: function () { return jws_js_2.verifyIssuerJws; } });
1031
+ Object.defineProperty(exports, "DEFAULT_TRUSTED_ISSUER_KIDS", { enumerable: true, get: function () { return jws_js_2.DEFAULT_TRUSTED_ISSUER_KIDS; } });
1032
+ Object.defineProperty(exports, "DEFAULT_TRUSTED_JWKS_HOSTS", { enumerable: true, get: function () { return jws_js_2.DEFAULT_TRUSTED_JWKS_HOSTS; } });
1033
+ Object.defineProperty(exports, "KEY_UNTRUSTED", { enumerable: true, get: function () { return jws_js_2.KEY_UNTRUSTED; } });
1034
+ Object.defineProperty(exports, "jwkThumbprint", { enumerable: true, get: function () { return jws_js_2.jwkThumbprint; } });
1035
+ Object.defineProperty(exports, "isPinnedTrustedJwk", { enumerable: true, get: function () { return jws_js_2.isPinnedTrustedJwk; } });
1036
+ Object.defineProperty(exports, "jwksHostAllowed", { enumerable: true, get: function () { return jws_js_2.jwksHostAllowed; } });
507
1037
  exports.default = {
508
1038
  verifyBinding,
509
1039
  verifyIssuerSignature,
@@ -521,7 +1051,10 @@ exports.default = {
521
1051
  computePaymentCommitment: binding_js_1.computePaymentCommitment,
522
1052
  computeInferenceBinding: binding_js_1.computeInferenceBinding,
523
1053
  canonicalIssuerPayload,
1054
+ canonicalPayloadVersion,
524
1055
  canonicalSignedPayload: exports.canonicalSignedPayload,
1056
+ reconcileSettledTransfer,
1057
+ sumUsdcTransfersToPayee,
525
1058
  hashCanonicalPayload,
526
1059
  ZK_VERIFIER_ADDRESS: exports.ZK_VERIFIER_ADDRESS,
527
1060
  BASE_RPC_URL: base_payer_js_1.BASE_RPC_URL,