@xfuel/verify 0.3.1 → 0.3.4

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.
Files changed (60) hide show
  1. package/README.md +16 -6
  2. package/dist/anchor-trust.d.ts +129 -0
  3. package/dist/anchor-trust.d.ts.map +1 -0
  4. package/dist/anchor-trust.js +395 -0
  5. package/dist/anchor-trust.js.map +1 -0
  6. package/dist/anchor-witness.d.ts +114 -4
  7. package/dist/anchor-witness.d.ts.map +1 -1
  8. package/dist/anchor-witness.js +419 -32
  9. package/dist/anchor-witness.js.map +1 -1
  10. package/dist/carry-forward.d.ts +79 -0
  11. package/dist/carry-forward.d.ts.map +1 -0
  12. package/dist/carry-forward.js +184 -0
  13. package/dist/carry-forward.js.map +1 -0
  14. package/dist/cli.js +691 -42
  15. package/dist/cli.js.map +1 -1
  16. package/dist/epoch.d.ts +41 -7
  17. package/dist/epoch.d.ts.map +1 -1
  18. package/dist/epoch.js +143 -0
  19. package/dist/epoch.js.map +1 -1
  20. package/dist/index.d.ts +65 -6
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +348 -96
  23. package/dist/index.js.map +1 -1
  24. package/dist/issuer-history.d.ts +19 -0
  25. package/dist/issuer-history.d.ts.map +1 -1
  26. package/dist/issuer-history.js +131 -75
  27. package/dist/issuer-history.js.map +1 -1
  28. package/dist/issuer-pin.d.ts +126 -0
  29. package/dist/issuer-pin.d.ts.map +1 -0
  30. package/dist/issuer-pin.js +567 -0
  31. package/dist/issuer-pin.js.map +1 -0
  32. package/dist/preimage.d.ts +29 -0
  33. package/dist/preimage.d.ts.map +1 -1
  34. package/dist/preimage.js +162 -2
  35. package/dist/preimage.js.map +1 -1
  36. package/dist/receipt-lane.d.ts +1 -0
  37. package/dist/receipt-lane.d.ts.map +1 -1
  38. package/dist/receipt-lane.js +5 -2
  39. package/dist/receipt-lane.js.map +1 -1
  40. package/dist/refusal.d.ts +5 -2
  41. package/dist/refusal.d.ts.map +1 -1
  42. package/dist/refusal.js +28 -6
  43. package/dist/refusal.js.map +1 -1
  44. package/dist/request-binding.d.ts +7 -0
  45. package/dist/request-binding.d.ts.map +1 -0
  46. package/dist/request-binding.js +26 -0
  47. package/dist/request-binding.js.map +1 -0
  48. package/dist/row-hash.d.ts +22 -0
  49. package/dist/row-hash.d.ts.map +1 -0
  50. package/dist/row-hash.js +26 -0
  51. package/dist/row-hash.js.map +1 -0
  52. package/dist/shell.d.ts +65 -0
  53. package/dist/shell.d.ts.map +1 -0
  54. package/dist/shell.js +268 -0
  55. package/dist/shell.js.map +1 -0
  56. package/dist/v11-receipt.d.ts +21 -0
  57. package/dist/v11-receipt.d.ts.map +1 -0
  58. package/dist/v11-receipt.js +351 -0
  59. package/dist/v11-receipt.js.map +1 -0
  60. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -17,13 +17,18 @@
17
17
  * 3 = input error
18
18
  */
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
+ const node_module_1 = require("node:module");
20
21
  const node_fs_1 = require("node:fs");
21
22
  const index_js_1 = require("./index.js");
22
23
  const anchor_witness_js_1 = require("./anchor-witness.js");
23
24
  const jws_js_1 = require("./jws.js");
25
+ const shell_js_1 = require("./shell.js");
24
26
  const preimage_js_1 = require("./preimage.js");
25
27
  const issuer_history_js_1 = require("./issuer-history.js");
26
28
  const canonical_preimage_js_1 = require("./canonical-preimage.js");
29
+ const v11_receipt_js_1 = require("./v11-receipt.js");
30
+ const issuer_pin_js_1 = require("./issuer-pin.js");
31
+ const anchor_trust_js_1 = require("./anchor-trust.js");
27
32
  const HELP = `
28
33
  xfuel-verify — Offline verification for Chit402 receipts
29
34
 
@@ -52,14 +57,35 @@ Options:
52
57
  --check-payer Query Base or Solana RPC. Base confirms payer, payee, asset, amount
53
58
  --solana-rpc <url> Solana RPC URL (default: https://api.mainnet-beta.solana.com or SOLANA_RPC_URL)
54
59
  --rpc <url> Base RPC URL (default: https://mainnet.base.org)
60
+ --version Print @xfuel/verify version and exit
55
61
  --rpc With a receipt, an inclusion proof, and a tree head: check the
56
62
  leaf, then the Solana memo and the Base calldata for that root.
63
+ The tree head must carry a valid ES256 issuer signature, and
64
+ the anchor wallets must match the signed head.
57
65
  A v2 head also needs the signed epoch record.
66
+ A tree-head file with --rpc enters this mode.
67
+ The same files without --rpc check the head signature and the
68
+ index/size-bound inclusion offline. They do not call chain RPC.
58
69
  --epoch-record <f> Signed epoch record JSON. Skips the network fetch.
59
70
  --epoch-url <url> GET this epoch record. Default: the receipt verify_url
60
71
  origin plus /v1/receipts/tree/epoch
61
72
  --inclusion <file> Inclusion proof JSON (chit402.inclusion.v1)
62
73
  --head <file> Signed tree head JSON (chit402.tree_head.v1 or v2)
74
+ --anchor-wallets-file <path>
75
+ Signed chit402.anchor_wallets.v1 document. Wallets in a
76
+ verified document are added to the package pin. A head
77
+ that names any other wallet fails.
78
+ --carry-old-head <file>
79
+ --carry-old-inclusion <file>
80
+ --carry-head <file>
81
+ --carry-inclusion <file>
82
+ Earlier signed head, its inclusion proof, the current
83
+ signed head, and the current inclusion proof. Repeat
84
+ --carry-inclusion for each current index of the leaf.
85
+ The issued time and epoch are the earlier head's signed
86
+ claims. The logged time, epoch, and leaf index are the
87
+ current head's signed claims. A receipt in the current
88
+ epoch does not use these flags.
63
89
  --json Output JSON instead of human-readable
64
90
  --quiet Only output errors
65
91
  --strict-issuer-history
@@ -83,8 +109,11 @@ Trust:
83
109
  issuer_jwk whose RFC 7638 thumbprint equals a pinned trusted kid. The
84
110
  embedded key alone is not a trust root. Untrusted keys report "key untrusted".
85
111
 
86
- Amount, payer, payee, and tx are read from verified signed claims. A mismatch
87
- with the unsigned outer payment / caller_binding copy is a failure.
112
+ Amount, payer, payee, and tx are read from verified signed claims. Every
113
+ signed field is compared with the outer receipt at the same path. A mismatch
114
+ fails. Outer fields the signature does not cover are printed as UNVERIFIED.
115
+ The outer document schema is not a copy of the JWS schema. Log mode (a
116
+ receipt, an inclusion proof, and a tree head) runs these receipt checks too.
88
117
 
89
118
  Network behavior:
90
119
  JWKS, nullifiers, and anchors are not fetched unless you pass a flag.
@@ -105,11 +134,34 @@ Anchored root:
105
134
  xfuel-verify receipt.json inclusion.json head.json --rpc
106
135
  xfuel-verify receipt.json --inclusion inclusion.json --head head.json --rpc https://mainnet.base.org
107
136
 
108
- Checks the Merkle inclusion, fetches the Solana transaction and requires the
109
- SPL Memo to contain the root, and checks the Base calldata the same way.
110
- Prints what this proves and what it does not prove. Exit 0 when both chains
111
- match, 2 when the leaf is included but an anchor is still pending, 1 when a
112
- check fails.
137
+ Also runs the single-receipt checks (issuer signature and signed-versus-outer
138
+ comparison). Those must pass before Overall can be VERIFIED. Then it checks
139
+ the tree head's ES256 issuer signature (production pin, issuer
140
+ history, or JWKS, same rules as a receipt). The signed root, size, epoch,
141
+ and anchors must match the head. Then it checks Merkle inclusion, requires
142
+ the Solana memo and fee payer, and requires the Base calldata and sender.
143
+ The sender and fee payer must be on the issuer anchor-wallet list (the
144
+ package pin, plus a verified /.well-known/anchor-wallets.json or
145
+ issuer-history anchor_wallets list). The on-chain issuer-root registry and
146
+ the DNS anchor are not read. Prints what this proves and what it does not
147
+ prove. Exit 0 when the receipt checks pass and both chains match, 2 when the
148
+ leaf is included but an anchor is still pending, or when the leaf is past
149
+ the anchored tree (PENDING, not a tamper failure), and the receipt checks
150
+ did not fail, 1 when a check fails. A pending leaf must sit at an index
151
+ at or past the anchored size, and PENDING applies only when the receipt
152
+ itself verifies. A failed signature, field, or preimage is a failure.
153
+ With --rpc, a claimed anchor must name a Solana fee payer that is on the
154
+ anchor-wallet list, and the head root must equal the newest memo that
155
+ payer actually paid. A memo from any other payer is ignored.
156
+
157
+ A receipt or head with issuer_key_pin is a pinned era. Pass the pin file
158
+ and its commit SHA plus SHA-256. The verifier does not fetch a branch.
159
+ --issuer-pin <file> --issuer-pin-commit <40-hex> --issuer-pin-sha256 <64-hex>
160
+ Optional with that pin: --issuer-pin-sig, --issuer-witnesses,
161
+ --issuer-prior-pin, --issuer-prior-commit, --issuer-prior-sha256, --issuer-control.
162
+ Those flags do not start the check on their own. A prior pin does not
163
+ replace the published specimen key. Sepolia only (eip155:84532).
164
+ See docs/product/issuer-key-pin.md.
113
165
 
114
166
  Receipt lane (unsigned, beside book_seq):
115
167
  settled_by is observed_transfer when the USDC transfer was checked on Base
@@ -138,6 +190,13 @@ Examples:
138
190
 
139
191
  # Pipe from curl
140
192
  curl -s https://api.chit402.com/receipt/task-123?format=json | xfuel-verify -
193
+
194
+ # Public shell. INCLUDED_SHELL is not VERIFIED.
195
+ xfuel-verify shell.json
196
+
197
+ # Owner view. VERIFIED needs a trusted issuer key, an inclusion proof,
198
+ # and a signed payment. The key inside the holder is not enough.
199
+ xfuel-verify shell.json --jws owner-view.json --inclusion inclusion.json --head head.json
141
200
  `;
142
201
  function parseArgs(args) {
143
202
  const result = {
@@ -157,6 +216,10 @@ function parseArgs(args) {
157
216
  headFile: null,
158
217
  epochRecordFile: null,
159
218
  epochUrl: null,
219
+ carryOldHeadFile: null,
220
+ carryOldInclusionFile: null,
221
+ carryHeadFile: null,
222
+ carryInclusionFiles: [],
160
223
  positionals: [],
161
224
  json: false,
162
225
  quiet: false,
@@ -166,10 +229,29 @@ function parseArgs(args) {
166
229
  canonicalPreimageFile: null,
167
230
  noIssuerHistory: false,
168
231
  noPreimage: false,
232
+ issuerPinFile: null,
233
+ issuerPinCommit: null,
234
+ issuerPinSha256: null,
235
+ issuerPinSigFile: null,
236
+ issuerWitnessesFile: null,
237
+ issuerPriorPinFile: null,
238
+ issuerPriorCommit: null,
239
+ issuerPriorSha256: null,
240
+ issuerControlFile: null,
241
+ version: false,
242
+ anchorWalletsFile: null,
243
+ acceptShell: false,
244
+ jwsFile: null,
245
+ salt: null,
246
+ open: [],
247
+ url: null,
169
248
  };
170
249
  for (let i = 0; i < args.length; i++) {
171
250
  const arg = args[i];
172
- if (arg === '--help' || arg === '-h') {
251
+ if (arg === '--version' || arg === '-V') {
252
+ result.version = true;
253
+ }
254
+ else if (arg === '--help' || arg === '-h') {
173
255
  result.help = true;
174
256
  }
175
257
  else if (arg === '--check-nullifier') {
@@ -214,6 +296,18 @@ function parseArgs(args) {
214
296
  else if (arg === '--epoch-url' && args[i + 1]) {
215
297
  result.epochUrl = args[++i];
216
298
  }
299
+ else if (arg === '--carry-old-head' && args[i + 1]) {
300
+ result.carryOldHeadFile = args[++i];
301
+ }
302
+ else if (arg === '--carry-old-inclusion' && args[i + 1]) {
303
+ result.carryOldInclusionFile = args[++i];
304
+ }
305
+ else if (arg === '--carry-head' && args[i + 1]) {
306
+ result.carryHeadFile = args[++i];
307
+ }
308
+ else if (arg === '--carry-inclusion' && args[i + 1]) {
309
+ result.carryInclusionFiles.push(args[++i]);
310
+ }
217
311
  else if (arg === '--solana-rpc' && args[i + 1]) {
218
312
  result.solanaRpcUrl = args[++i];
219
313
  }
@@ -223,15 +317,60 @@ function parseArgs(args) {
223
317
  else if (arg === '--issuer-history-file' && args[i + 1]) {
224
318
  result.issuerHistoryFile = args[++i];
225
319
  }
320
+ else if (arg === '--salt' && args[i + 1]) {
321
+ result.salt = args[++i];
322
+ }
323
+ else if (arg === '--open' && args[i + 1]) {
324
+ result.open.push(args[++i]);
325
+ }
226
326
  else if (arg === '--canonical-preimage' && args[i + 1]) {
227
327
  result.canonicalPreimageFile = args[++i];
228
328
  }
229
329
  else if (arg === '--no-issuer-history') {
230
330
  result.noIssuerHistory = true;
231
331
  }
332
+ else if (arg === '--anchor-wallets-file' && args[i + 1]) {
333
+ result.anchorWalletsFile = args[++i];
334
+ }
232
335
  else if (arg === '--no-preimage') {
233
336
  result.noPreimage = true;
234
337
  }
338
+ else if (arg === '--accept-shell') {
339
+ result.acceptShell = true;
340
+ }
341
+ else if (arg === '--jws' && args[i + 1]) {
342
+ result.jwsFile = args[++i];
343
+ }
344
+ else if (arg === '--url' && args[i + 1]) {
345
+ result.url = args[++i];
346
+ }
347
+ else if (arg === '--issuer-pin' && args[i + 1]) {
348
+ result.issuerPinFile = args[++i];
349
+ }
350
+ else if (arg === '--issuer-pin-commit' && args[i + 1]) {
351
+ result.issuerPinCommit = args[++i];
352
+ }
353
+ else if (arg === '--issuer-pin-sha256' && args[i + 1]) {
354
+ result.issuerPinSha256 = args[++i];
355
+ }
356
+ else if (arg === '--issuer-pin-sig' && args[i + 1]) {
357
+ result.issuerPinSigFile = args[++i];
358
+ }
359
+ else if (arg === '--issuer-witnesses' && args[i + 1]) {
360
+ result.issuerWitnessesFile = args[++i];
361
+ }
362
+ else if (arg === '--issuer-prior-pin' && args[i + 1]) {
363
+ result.issuerPriorPinFile = args[++i];
364
+ }
365
+ else if (arg === '--issuer-prior-commit' && args[i + 1]) {
366
+ result.issuerPriorCommit = args[++i];
367
+ }
368
+ else if (arg === '--issuer-prior-sha256' && args[i + 1]) {
369
+ result.issuerPriorSha256 = args[++i];
370
+ }
371
+ else if (arg === '--issuer-control' && args[i + 1]) {
372
+ result.issuerControlFile = args[++i];
373
+ }
235
374
  else if (arg === '--json') {
236
375
  result.json = true;
237
376
  }
@@ -310,7 +449,18 @@ function printAnchor(result, json, quiet) {
310
449
  console.log(' Anchored receipt root');
311
450
  console.log(' ─────────────────────────────────────────────────');
312
451
  console.log(` Root: ${result.root || '—'}`);
313
- console.log(` Inclusion: ${mark(result.inclusion.valid)}${result.inclusion.reason ? ` (${result.inclusion.reason})` : ''}`);
452
+ console.log(` Head signature:${result.head_signature.valid ? ' ✓ YES' : ' ✗ NO'}${result.head_signature.trust ? ` (${result.head_signature.trust})` : ''}`);
453
+ if (result.head_signature.kid)
454
+ console.log(` Head kid: ${result.head_signature.kid}`);
455
+ if (result.head_signature.message)
456
+ console.log(` Head reason: ${result.head_signature.message}`);
457
+ const unlogged = result.inclusion.unlogged_reason ? ` unlogged:${result.inclusion.unlogged_reason}` : '';
458
+ if (result.overall === 'pending') {
459
+ console.log(` Inclusion: PENDING${result.inclusion.reason ? ` (${result.inclusion.reason})` : ''}`);
460
+ }
461
+ else {
462
+ console.log(` Inclusion: ${mark(result.inclusion.valid)}${result.inclusion.reason ? ` (${result.inclusion.reason})` : ''}${unlogged}`);
463
+ }
314
464
  console.log(` Leaf source: ${result.inclusion.leaf_source}`);
315
465
  console.log(` Solana: ${result.solana.checked ? mark(result.solana.valid) : (result.solana.reason || 'not checked')}`);
316
466
  if (result.solana.signature)
@@ -319,11 +469,18 @@ function printAnchor(result, json, quiet) {
319
469
  console.log(` Slot: ${result.solana.slot}`);
320
470
  if (result.solana.cluster)
321
471
  console.log(` Cluster: ${result.solana.cluster}`);
472
+ if (result.solana.fee_payer)
473
+ console.log(` Fee payer: ${result.solana.fee_payer}`);
322
474
  if (result.solana.memo)
323
475
  console.log(` Memo: ${result.solana.memo}`);
324
476
  if (result.solana.reason && !result.solana.valid)
325
477
  console.log(` Solana reason: ${result.solana.reason}`);
478
+ if (result.issuer_pin?.checked) {
479
+ console.log(` Issuer pin: ${result.issuer_pin.ok ? '✓ YES' : '✗ NO'}${result.issuer_pin.code ? ` (${result.issuer_pin.code})` : ''}`);
480
+ }
326
481
  console.log(` Base: ${result.base.checked ? mark(result.base.valid) : (result.base.reason || 'not checked')}`);
482
+ if (result.base.from)
483
+ console.log(` Base from: ${result.base.from}`);
327
484
  if (result.base.tx)
328
485
  console.log(` Base tx: ${result.base.tx}`);
329
486
  if (result.base.chain_id != null)
@@ -341,9 +498,16 @@ function printAnchor(result, json, quiet) {
341
498
  for (const line of result.does_not_prove)
342
499
  console.log(` ${line}`);
343
500
  console.log('');
501
+ if (result.overall === 'pending') {
502
+ console.log(` Anchored size: ${result.inclusion.anchored_tree_size ?? '—'}`);
503
+ console.log(` Live size: ${result.inclusion.live_tree_size ?? '—'}`);
504
+ }
344
505
  console.log(` Overall: ${result.overall.toUpperCase()}`);
345
506
  if (result.errors.length > 0)
346
507
  console.log(` Errors: ${result.errors.join(', ')}`);
508
+ if (result.errors.includes(anchor_trust_js_1.LEGACY_HEAD_UNPINNED_SIGNER)) {
509
+ console.log(` ${anchor_trust_js_1.HEAD_TRUST_MESSAGES[anchor_trust_js_1.LEGACY_HEAD_UNPINNED_SIGNER]}`);
510
+ }
347
511
  console.log('');
348
512
  }
349
513
  function headNeedsEpochRecord(head) {
@@ -403,20 +567,139 @@ function epochSignatureOk(record, jws, jwks, trustedKids) {
403
567
  return false;
404
568
  if (JSON.stringify(payload.orphans ?? []) !== JSON.stringify(record.orphans ?? []))
405
569
  return false;
570
+ const payloadUnlogged = payload.unlogged === undefined ? null : payload.unlogged;
571
+ const recordUnlogged = record.unlogged === undefined ? null : record.unlogged;
572
+ if (JSON.stringify(payloadUnlogged) !== JSON.stringify(recordUnlogged))
573
+ return false;
406
574
  return true;
407
575
  }
408
576
  return false;
409
577
  }
578
+ function packageVersion() {
579
+ const require = (0, node_module_1.createRequire)(__filename);
580
+ const pkg = require('../package.json');
581
+ return pkg.version || '0.0.0';
582
+ }
583
+ function looksLikeTreeHead(value) {
584
+ if (!value || typeof value !== 'object')
585
+ return false;
586
+ const doc = value;
587
+ if (doc.status === 'not_yet_published')
588
+ return true;
589
+ if (typeof doc.schema === 'string' && doc.schema.startsWith('chit402.tree_head.'))
590
+ return true;
591
+ return Boolean(doc.anchors && doc.root && !doc.task_id);
592
+ }
593
+ function readOfflineWitness(args) {
594
+ let head = null;
595
+ let inclusion = null;
596
+ for (const file of args.positionals.slice(1)) {
597
+ let doc;
598
+ try {
599
+ doc = readJson(file);
600
+ }
601
+ catch (err) {
602
+ throw new Error(`Error reading ${file}: ${err instanceof Error ? err.message : String(err)}`);
603
+ }
604
+ const row = doc;
605
+ if (looksLikeTreeHead(doc))
606
+ head = doc;
607
+ else if (doc && typeof doc === 'object' && (Array.isArray(row.proof) || row.leaf_index != null)) {
608
+ inclusion = doc;
609
+ }
610
+ }
611
+ return { head, inclusion };
612
+ }
613
+ function fileLooksLikeHead(file) {
614
+ if (!file)
615
+ return false;
616
+ try {
617
+ return looksLikeTreeHead(readJson(file));
618
+ }
619
+ catch {
620
+ return false;
621
+ }
622
+ }
623
+ function historyHostAllowed(url) {
624
+ try {
625
+ const parsed = new URL(url);
626
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
627
+ return false;
628
+ if (parsed.protocol === 'http:' && parsed.hostname !== '127.0.0.1' && parsed.hostname !== 'localhost')
629
+ return false;
630
+ if (parsed.protocol === 'https:') {
631
+ return index_js_1.DEFAULT_TRUSTED_JWKS_HOSTS.some((host) => host.toLowerCase() === parsed.hostname.toLowerCase());
632
+ }
633
+ return true;
634
+ }
635
+ catch {
636
+ return false;
637
+ }
638
+ }
639
+ async function loadAnchorIssuerHistory(args, receipt) {
640
+ if (args.issuerHistoryFile) {
641
+ return JSON.parse((0, node_fs_1.readFileSync)(args.issuerHistoryFile, 'utf8'));
642
+ }
643
+ if (args.noIssuerHistory)
644
+ return null;
645
+ const url = (0, issuer_history_js_1.historyUrlFromReceipt)(receipt);
646
+ if (!url || !historyHostAllowed(url))
647
+ return null;
648
+ try {
649
+ const res = await fetch(url, { signal: AbortSignal.timeout(4000) });
650
+ if (!res.ok)
651
+ return null;
652
+ return await res.json();
653
+ }
654
+ catch {
655
+ return null;
656
+ }
657
+ }
658
+ async function loadAnchorWalletDocument(args, receipt) {
659
+ if (args.anchorWalletsFile) {
660
+ return JSON.parse((0, node_fs_1.readFileSync)(args.anchorWalletsFile, 'utf8'));
661
+ }
662
+ if (args.noIssuerHistory)
663
+ return null;
664
+ const historyUrl = (0, issuer_history_js_1.historyUrlFromReceipt)(receipt);
665
+ const epochOrigin = (() => {
666
+ if (!args.epochUrl)
667
+ return null;
668
+ try {
669
+ return new URL(args.epochUrl).origin;
670
+ }
671
+ catch {
672
+ return null;
673
+ }
674
+ })();
675
+ const origin = epochOrigin || (historyUrl ? new URL(historyUrl).origin : null);
676
+ if (!origin)
677
+ return null;
678
+ const url = `${origin}/.well-known/anchor-wallets.json`;
679
+ if (!historyHostAllowed(url))
680
+ return null;
681
+ try {
682
+ const res = await fetch(url, { signal: AbortSignal.timeout(4000) });
683
+ if (res.status === 404)
684
+ return null;
685
+ if (!res.ok)
686
+ return null;
687
+ return await res.json();
688
+ }
689
+ catch {
690
+ return null;
691
+ }
692
+ }
410
693
  async function runAnchor(args) {
411
694
  const receiptPath = args.file;
412
695
  const inclusionPath = args.inclusionFile || args.positionals[1] || null;
413
696
  const headPath = args.headFile || (args.inclusionFile ? null : args.positionals[2]) || null;
697
+ const headCandidates = [args.headFile, headPath, ...args.positionals];
698
+ if (headCandidates.some((file) => file && headStatus(file) === 'not_yet_published')) {
699
+ console.error('Tree head is not_yet_published.');
700
+ return 3;
701
+ }
414
702
  if (!receiptPath || !inclusionPath || !headPath) {
415
- const candidates = [args.headFile, headPath, ...args.positionals];
416
- if (candidates.some((file) => file && headStatus(file) === 'not_yet_published')) {
417
- console.error('Tree head is not_yet_published.');
418
- return 3;
419
- }
420
703
  console.error('Anchor check needs a receipt, an inclusion proof, and a tree head.');
421
704
  console.error(' xfuel-verify receipt.json inclusion.json head.json --rpc');
422
705
  return 3;
@@ -433,6 +716,34 @@ async function runAnchor(args) {
433
716
  console.error(`Error reading anchor inputs: ${err instanceof Error ? err.message : String(err)}`);
434
717
  return 3;
435
718
  }
719
+ let fileJwks;
720
+ if (args.jwksFile) {
721
+ try {
722
+ fileJwks = JSON.parse((0, node_fs_1.readFileSync)(args.jwksFile, 'utf8'));
723
+ }
724
+ catch (err) {
725
+ console.error(`Error reading JWKS file: ${err instanceof Error ? err.message : String(err)}`);
726
+ return 3;
727
+ }
728
+ }
729
+ const loaded = await (0, index_js_1.loadIssuerJwks)(receipt, {
730
+ jwks: fileJwks,
731
+ jwksUri: args.jwksUrl || undefined,
732
+ fetchJwks: args.fetchJwks,
733
+ });
734
+ const trustedKids = args.noTrustedKid
735
+ ? []
736
+ : (args.trustedKids ?? [...index_js_1.DEFAULT_TRUSTED_ISSUER_KIDS]);
737
+ let issuerHistory = null;
738
+ let anchorWalletDocument = null;
739
+ try {
740
+ issuerHistory = await loadAnchorIssuerHistory(args, receipt);
741
+ anchorWalletDocument = await loadAnchorWalletDocument(args, receipt);
742
+ }
743
+ catch (err) {
744
+ console.error(`Error reading issuer trust files: ${err instanceof Error ? err.message : String(err)}`);
745
+ return 3;
746
+ }
436
747
  let epochRecord = null;
437
748
  let verifyEpochSignature;
438
749
  if (headNeedsEpochRecord(head)) {
@@ -443,25 +754,46 @@ async function runAnchor(args) {
443
754
  epochRecord = null;
444
755
  }
445
756
  if (epochRecord?.issuer_signature?.jws) {
446
- let fileJwks;
447
- if (args.jwksFile) {
448
- try {
449
- fileJwks = JSON.parse((0, node_fs_1.readFileSync)(args.jwksFile, 'utf8'));
757
+ verifyEpochSignature = (jws) => epochSignatureOk(epochRecord, jws, loaded.jwks, trustedKids);
758
+ }
759
+ }
760
+ let issuerPin;
761
+ const pinClaim = (0, issuer_pin_js_1.readIssuerPinClaim)(receipt).claimed || (0, issuer_pin_js_1.readIssuerPinClaim)(head).claimed;
762
+ const pinRequested = Boolean(args.issuerPinFile) || pinClaim;
763
+ if (pinRequested) {
764
+ try {
765
+ const claimRef = (0, issuer_pin_js_1.readIssuerPinClaim)(receipt).ref || (0, issuer_pin_js_1.readIssuerPinClaim)(head).ref;
766
+ const ref = (args.issuerPinCommit || args.issuerPinSha256 || claimRef)
767
+ ? {
768
+ commit: args.issuerPinCommit || claimRef?.commit || '',
769
+ path: claimRef?.path || 'docs/well-known/issuer-key.json',
770
+ sha256: (args.issuerPinSha256 || claimRef?.sha256 || '').toLowerCase(),
450
771
  }
451
- catch (err) {
452
- console.error(`Error reading JWKS file: ${err instanceof Error ? err.message : String(err)}`);
453
- return 3;
772
+ : null;
773
+ let witnesses;
774
+ if (args.issuerWitnessesFile)
775
+ witnesses = JSON.parse((0, node_fs_1.readFileSync)(args.issuerWitnessesFile, 'utf8'));
776
+ const priorRef = args.issuerPriorPinFile
777
+ ? {
778
+ commit: args.issuerPriorCommit || '',
779
+ path: 'docs/well-known/issuer-key.json',
780
+ sha256: (args.issuerPriorSha256 || '').toLowerCase(),
454
781
  }
455
- }
456
- const loaded = await (0, index_js_1.loadIssuerJwks)(receipt, {
457
- jwks: fileJwks,
458
- jwksUri: args.jwksUrl || undefined,
459
- fetchJwks: args.fetchJwks,
460
- });
461
- const trustedKids = args.noTrustedKid
462
- ? []
463
- : (args.trustedKids ?? [...index_js_1.DEFAULT_TRUSTED_ISSUER_KIDS]);
464
- verifyEpochSignature = (jws) => epochSignatureOk(epochRecord, jws, loaded.jwks, trustedKids);
782
+ : null;
783
+ issuerPin = {
784
+ pinBytes: args.issuerPinFile ? (0, node_fs_1.readFileSync)(args.issuerPinFile, 'utf8') : null,
785
+ sigBytes: args.issuerPinSigFile ? (0, node_fs_1.readFileSync)(args.issuerPinSigFile, 'utf8') : null,
786
+ ref,
787
+ witnesses,
788
+ priorPinBytes: args.issuerPriorPinFile ? (0, node_fs_1.readFileSync)(args.issuerPriorPinFile, 'utf8') : null,
789
+ priorRef,
790
+ controlJws: args.issuerControlFile ? (0, node_fs_1.readFileSync)(args.issuerControlFile, 'utf8').trim() : null,
791
+ required: true,
792
+ };
793
+ }
794
+ catch (err) {
795
+ console.error(`Error reading issuer pin: ${err instanceof Error ? err.message : String(err)}`);
796
+ return 3;
465
797
  }
466
798
  }
467
799
  const result = await (0, anchor_witness_js_1.verifyAnchoredRoot)({
@@ -470,25 +802,112 @@ async function runAnchor(args) {
470
802
  head,
471
803
  baseRpcUrl: args.rpcUrl || undefined,
472
804
  solanaRpcUrl: args.solanaRpcUrl || undefined,
805
+ checkNewestAnchor: args.sawRpc,
473
806
  epochRecord,
474
807
  verifyEpochSignature,
808
+ issuerPin,
809
+ jwks: loaded.jwks,
810
+ trustedKids,
811
+ issuerHistory,
812
+ strictIssuerHistory: args.strictIssuerHistory,
813
+ anchorWalletDocument: anchorWalletDocument,
814
+ });
815
+ let canonicalPreimage = null;
816
+ if (args.canonicalPreimageFile) {
817
+ canonicalPreimage = (0, node_fs_1.readFileSync)(args.canonicalPreimageFile, 'utf8');
818
+ }
819
+ const historyBytes = args.issuerHistoryFile ? (0, node_fs_1.readFileSync)(args.issuerHistoryFile, 'utf8') : null;
820
+ const verified = await (0, index_js_1.verifyReceipt)(receipt, {
821
+ jwks: loaded.jwks,
822
+ jwksUri: args.jwksUrl || undefined,
823
+ fetchJwks: args.fetchJwks,
824
+ trustedKids,
825
+ checkNullifier: args.checkNullifier,
826
+ checkPayer: args.checkPayer,
827
+ rpcUrl: args.rpcUrl || undefined,
828
+ solanaRpcUrl: args.solanaRpcUrl || undefined,
829
+ requirePreimages: !args.noPreimage,
830
+ issuerHistory: args.issuerHistoryFile ? issuerHistory : null,
831
+ issuerHistoryBytes: historyBytes,
832
+ fetchIssuerHistory: !args.noIssuerHistory && !args.issuerHistoryFile,
833
+ strictIssuerHistory: args.strictIssuerHistory,
834
+ skipIssuerHistory: args.noIssuerHistory,
835
+ canonicalPreimage,
836
+ head,
475
837
  });
476
- const verified = await (0, index_js_1.verifyReceipt)(receipt, { head });
477
- const lane = verified.receipt_lane;
838
+ // Inclusion was already checked above. Passing it again re-derives the leaf
839
+ // from book_chain.row_hash only, which rejects a receipt that stores row_hash
840
+ // at the top level. Log mode still requires the receipt itself. A missing or
841
+ // stripped issuer signature, a partial check, or a signed-versus-outer
842
+ // mismatch fails the combined result. An inclusion match is not VERIFIED.
843
+ const issuerSig = receipt.issuer_signature;
844
+ const signaturePresent = Boolean(issuerSig?.jws || issuerSig?.value);
845
+ const receiptVerified = signaturePresent
846
+ && verified.issuer_signature.valid === true
847
+ && (verified.overall === 'verified' || verified.overall === 'verified_carried_forward')
848
+ && verified.claim_mismatches.length === 0;
849
+ const receiptFailed = !receiptVerified;
850
+ const receiptErrors = [...verified.errors];
851
+ if (!signaturePresent) {
852
+ receiptErrors.push(verified.issuer_signature.reason || 'receipt_signature_missing');
853
+ }
854
+ else if (verified.overall === 'partial') {
855
+ receiptErrors.push(verified.issuer_signature.reason || 'receipt_check_partial');
856
+ }
857
+ const errors = receiptFailed
858
+ ? [...result.errors, ...receiptErrors.filter((err) => !result.errors.includes(err))]
859
+ : result.errors;
860
+ const combined = {
861
+ ...result,
862
+ errors,
863
+ overall: receiptFailed ? 'failed' : result.overall,
864
+ receipt_lane: verified.receipt_lane,
865
+ receipt_check: {
866
+ overall: result.overall === 'pending' && !receiptFailed ? 'pending' : verified.overall,
867
+ verified_scope: verified.verified_scope,
868
+ claim_mismatches: verified.claim_mismatches,
869
+ unsigned_fields: verified.unsigned_fields,
870
+ errors: verified.errors,
871
+ },
872
+ };
478
873
  if (args.json) {
479
- console.log(JSON.stringify({ ...result, receipt_lane: lane }, null, 2));
874
+ console.log(JSON.stringify(combined, null, 2));
480
875
  }
481
876
  else {
482
- printAnchor(result, false, args.quiet);
877
+ printAnchor(combined, false, args.quiet);
878
+ if (!args.quiet || receiptFailed) {
879
+ const anchorPending = result.overall === 'pending';
880
+ const receiptLabel = anchorPending && !receiptFailed
881
+ ? 'PENDING'
882
+ : verified.overall.toUpperCase();
883
+ console.log(` Receipt checks: ${receiptLabel} (signed claims only)`);
884
+ if (verified.claim_mismatches.length > 0) {
885
+ for (const mismatch of verified.claim_mismatches) {
886
+ console.log(` ${mismatch.field}: outer ${mismatch.outer} ≠ signed ${mismatch.signed}`);
887
+ }
888
+ }
889
+ if (!(anchorPending && !receiptFailed))
890
+ printUnsigned(verified.unsigned_fields);
891
+ console.log('');
892
+ }
483
893
  if (!args.quiet)
484
- printLane(lane);
894
+ printLane(verified.receipt_lane);
485
895
  }
486
- if (result.overall === 'verified')
896
+ if (combined.overall === 'verified')
487
897
  return 0;
488
- if (result.overall === 'partial')
898
+ if (combined.overall === 'partial' || combined.overall === 'pending')
489
899
  return 2;
490
900
  return 1;
491
901
  }
902
+ function printUnsigned(fields) {
903
+ console.log(' Scope: signed claims only. Unsigned fields are UNVERIFIED.');
904
+ if (fields.length === 0)
905
+ return;
906
+ console.log(` Unsigned fields (${fields.length})`);
907
+ console.log(' ─────────────────────────────────────────────────');
908
+ for (const field of fields)
909
+ console.log(` UNVERIFIED ${field}`);
910
+ }
492
911
  async function runRefusal(doc, args) {
493
912
  const trustedKids = args.trustedKids ?? index_js_1.DEFAULT_TRUSTED_ISSUER_KIDS;
494
913
  const result = (0, index_js_1.verifyRefusal)(doc, {
@@ -511,7 +930,7 @@ async function runRefusal(doc, args) {
511
930
  issuer_history: result.issuer_history,
512
931
  }) : null;
513
932
  const history = args.skipIssuerHistory
514
- ? { checked: false, ok: true, unreachable: false, warning: null, reason: null, kid: result.kid ?? null }
933
+ ? { checked: false, ok: true, unreachable: false, warning: null, reason: null, kid: result.kid ?? null, document: null, loaded: false }
515
934
  : await (0, issuer_history_js_1.checkReceiptIssuerHistory)(doc, {
516
935
  document: args.issuerHistory,
517
936
  documentBytes: args.issuerHistoryBytes,
@@ -571,14 +990,45 @@ async function runRefusal(doc, args) {
571
990
  }
572
991
  async function main() {
573
992
  const args = parseArgs(process.argv.slice(2));
574
- const anchorMode = args.anchorFlag
993
+ if (args.version) {
994
+ console.log(`@xfuel/verify ${packageVersion()}`);
995
+ return 0;
996
+ }
997
+ let anchorMode = args.anchorFlag
575
998
  || Boolean(args.inclusionFile || args.headFile)
576
999
  || (args.positionals.length >= 3 && args.sawRpc);
1000
+ if (!anchorMode && args.sawRpc && args.positionals.some((file) => fileLooksLikeHead(file))) {
1001
+ anchorMode = true;
1002
+ }
577
1003
  if (args.help) {
578
1004
  console.log(HELP);
579
1005
  return 0;
580
1006
  }
581
- if (anchorMode)
1007
+ if (args.url && !args.jwsFile) {
1008
+ try {
1009
+ const response = await fetch(args.url);
1010
+ await response.arrayBuffer();
1011
+ }
1012
+ catch {
1013
+ // A failed fetch is still not a pass.
1014
+ }
1015
+ console.log('owner_proof_required');
1016
+ console.log(' Overall: OWNER_PROOF_REQUIRED');
1017
+ return 1;
1018
+ }
1019
+ let offlineWitness = null;
1020
+ if (!anchorMode) {
1021
+ try {
1022
+ offlineWitness = readOfflineWitness(args);
1023
+ }
1024
+ catch (err) {
1025
+ console.error(err instanceof Error ? err.message : String(err));
1026
+ return 3;
1027
+ }
1028
+ }
1029
+ // A shell plus a holder is not anchor mode. Inclusion is checked with the
1030
+ // holder. Anchor mode would verify the unsigned shell and skip the key.
1031
+ if (anchorMode && !args.jwsFile && !args.acceptShell)
582
1032
  return runAnchor(args);
583
1033
  if (!args.file) {
584
1034
  console.log(HELP);
@@ -592,7 +1042,150 @@ async function main() {
592
1042
  console.error(`Error reading receipt: ${err instanceof Error ? err.message : String(err)}`);
593
1043
  return 3;
594
1044
  }
595
- if (!receipt.task_id) {
1045
+ if ((0, shell_js_1.isReceiptShell)(receipt) || args.acceptShell || args.jwsFile) {
1046
+ if (!(0, shell_js_1.isReceiptShell)(receipt)) {
1047
+ console.error('Invalid shell: schema is not chit402.receipt_shell.v1');
1048
+ return 3;
1049
+ }
1050
+ if (!args.jwsFile) {
1051
+ console.log(shell_js_1.INCLUDED_SHELL_LINE);
1052
+ console.log(' Overall: INCLUDED_SHELL');
1053
+ return args.acceptShell ? 0 : 1;
1054
+ }
1055
+ let holder;
1056
+ try {
1057
+ const text = (0, node_fs_1.readFileSync)(args.jwsFile, 'utf8');
1058
+ let parsed = text;
1059
+ try {
1060
+ parsed = JSON.parse(text);
1061
+ }
1062
+ catch {
1063
+ parsed = text;
1064
+ }
1065
+ holder = (0, shell_js_1.holderDocumentFromOwnerView)(parsed);
1066
+ }
1067
+ catch (err) {
1068
+ console.error(`Error reading JWS: ${err instanceof Error ? err.message : String(err)}`);
1069
+ return 3;
1070
+ }
1071
+ const shellDoc = receipt;
1072
+ const compared = (0, shell_js_1.compareShellToJws)(shellDoc, holder);
1073
+ if (!compared.ok) {
1074
+ console.log('shell_jws_mismatch');
1075
+ if (compared.field)
1076
+ console.log(` Field: ${compared.field}`);
1077
+ console.log(' Overall: FAILED');
1078
+ return 1;
1079
+ }
1080
+ let jwks;
1081
+ if (args.jwksFile) {
1082
+ try {
1083
+ jwks = JSON.parse((0, node_fs_1.readFileSync)(args.jwksFile, 'utf8'));
1084
+ }
1085
+ catch (err) {
1086
+ console.error(`Error reading JWKS file: ${err instanceof Error ? err.message : String(err)}`);
1087
+ return 3;
1088
+ }
1089
+ }
1090
+ let inclusion = offlineWitness?.inclusion ?? null;
1091
+ let head = offlineWitness?.head ?? null;
1092
+ try {
1093
+ if (args.inclusionFile)
1094
+ inclusion = readJson(args.inclusionFile);
1095
+ if (args.headFile)
1096
+ head = readJson(args.headFile);
1097
+ }
1098
+ catch (err) {
1099
+ console.error(`Error reading inclusion: ${err instanceof Error ? err.message : String(err)}`);
1100
+ return 3;
1101
+ }
1102
+ const trustedKids = args.noTrustedKid
1103
+ ? []
1104
+ : (args.trustedKids ?? [...index_js_1.DEFAULT_TRUSTED_ISSUER_KIDS]);
1105
+ const result = await (0, index_js_1.verifyReceipt)(holder, {
1106
+ jwks,
1107
+ jwksUri: args.jwksUrl || undefined,
1108
+ fetchJwks: args.fetchJwks,
1109
+ trustedKids,
1110
+ checkPayer: args.checkPayer,
1111
+ rpcUrl: args.rpcUrl || undefined,
1112
+ solanaRpcUrl: args.solanaRpcUrl || undefined,
1113
+ requirePreimages: false,
1114
+ skipIssuerHistory: args.noIssuerHistory || !args.issuerHistoryFile,
1115
+ issuerHistory: null,
1116
+ fetchIssuerHistory: false,
1117
+ salt: args.salt,
1118
+ head: head ?? undefined,
1119
+ inclusion: inclusion ?? undefined,
1120
+ });
1121
+ const inclusionErrors = result.errors.filter((err) => /inclusion|leaf|row_hash|bad_root|tree_size|tree_head|no_leaf/.test(err));
1122
+ const shellLeaf = (shellDoc.inclusion && typeof shellDoc.inclusion === 'object')
1123
+ ? shellDoc.inclusion.leaf_hash
1124
+ : null;
1125
+ const provedLeaf = inclusion && typeof inclusion.leaf === 'string'
1126
+ ? inclusion.leaf
1127
+ : null;
1128
+ // A head the user wrote by hand ({root, tree_size}) is not a log. The
1129
+ // inclusion only counts against a head that carries an issuer JWS;
1130
+ // verifyReceipt() checks that JWS against the same trust set.
1131
+ const headSig = head && typeof head === 'object'
1132
+ ? head.issuer_signature
1133
+ : null;
1134
+ const headSigned = !!headSig && typeof headSig.jws === 'string' && headSig.jws.split('.').length === 3;
1135
+ const verdict = (0, shell_js_1.shellHolderVerdict)({
1136
+ signatureValid: result.issuer_signature.valid === true,
1137
+ keyTrusted: result.issuer_signature.key_trusted === true,
1138
+ signatureReason: result.issuer_signature.reason || (result.issuer_signature.key_trusted ? null : 'key untrusted'),
1139
+ inclusionSupplied: inclusion != null,
1140
+ inclusionOk: inclusion != null && inclusionErrors.length === 0,
1141
+ inclusionReason: inclusion == null ? 'inclusion_missing' : (inclusionErrors[0] || null),
1142
+ headSigned,
1143
+ paymentRef: result.tx,
1144
+ bindingExpected: !!result.binding.expected,
1145
+ bindingMatches: result.binding.matches === true,
1146
+ shellLeaf: shellLeaf == null ? null : String(shellLeaf),
1147
+ provedLeaf,
1148
+ });
1149
+ if (!verdict.verified || (result.overall !== 'verified' && result.overall !== 'verified_carried_forward')) {
1150
+ const reason = verdict.reason || result.errors[0] || 'failed';
1151
+ console.log(reason);
1152
+ console.log(' Overall: FAILED');
1153
+ return 1;
1154
+ }
1155
+ if (args.checkPayer && !result.payer.valid) {
1156
+ console.log(result.payer.reason || 'payment_unchecked');
1157
+ console.log(' Overall: FAILED');
1158
+ return 1;
1159
+ }
1160
+ const issuer = holder.issuer_signature;
1161
+ if (args.salt) {
1162
+ const opened = (0, shell_js_1.openV11Commitment)({
1163
+ payload: (0, shell_js_1.decodeJwsPayload)(issuer?.jws),
1164
+ saltHex: args.salt || '',
1165
+ body: Buffer.alloc(0),
1166
+ });
1167
+ if (!opened.ok) {
1168
+ console.log(opened.reason || 'commitment_mismatch');
1169
+ console.log(' Overall: FAILED');
1170
+ return 1;
1171
+ }
1172
+ }
1173
+ console.log(' Overall: VERIFIED');
1174
+ return 0;
1175
+ }
1176
+ const v11 = receipt.v === 11 || (receipt.issuer_signature?.jws
1177
+ ? (() => {
1178
+ try {
1179
+ const part = String(receipt.issuer_signature.jws).split('.')[1];
1180
+ const payload = JSON.parse(Buffer.from(part, 'base64url').toString('utf8'));
1181
+ return payload?.v === 11;
1182
+ }
1183
+ catch {
1184
+ return false;
1185
+ }
1186
+ })()
1187
+ : false);
1188
+ if (!receipt.task_id && !v11) {
596
1189
  console.error('Invalid receipt: missing task_id');
597
1190
  return 3;
598
1191
  }
@@ -658,6 +1251,42 @@ async function main() {
658
1251
  return 3;
659
1252
  }
660
1253
  }
1254
+ const carryAsked = args.carryOldHeadFile || args.carryOldInclusionFile
1255
+ || args.carryHeadFile || args.carryInclusionFiles.length > 0;
1256
+ let carry = null;
1257
+ if (carryAsked) {
1258
+ try {
1259
+ carry = {
1260
+ oldHead: args.carryOldHeadFile ? readJson(args.carryOldHeadFile) : {},
1261
+ oldInclusion: args.carryOldInclusionFile ? readJson(args.carryOldInclusionFile) : { error: 'not_in_tree' },
1262
+ currentHead: args.carryHeadFile ? readJson(args.carryHeadFile) : {},
1263
+ currentInclusions: args.carryInclusionFiles.length > 0
1264
+ ? args.carryInclusionFiles.map((file) => readJson(file))
1265
+ : [{ error: 'not_in_tree' }],
1266
+ };
1267
+ }
1268
+ catch (err) {
1269
+ console.error(`Error reading carry-forward files: ${err instanceof Error ? err.message : String(err)}`);
1270
+ return 3;
1271
+ }
1272
+ }
1273
+ let open;
1274
+ if (args.open.length > 0) {
1275
+ open = {};
1276
+ try {
1277
+ for (const spec of args.open) {
1278
+ const opened = (0, v11_receipt_js_1.readOpenFile)(spec);
1279
+ if (opened.kind === 'output')
1280
+ open.output = opened.bytes;
1281
+ else
1282
+ open[opened.kind] = opened.text;
1283
+ }
1284
+ }
1285
+ catch (err) {
1286
+ console.error(err instanceof Error ? err.message : String(err));
1287
+ return 3;
1288
+ }
1289
+ }
661
1290
  const result = await (0, index_js_1.verifyReceipt)(receipt, {
662
1291
  jwks,
663
1292
  jwksUri: args.jwksUrl || undefined,
@@ -674,6 +1303,11 @@ async function main() {
674
1303
  strictIssuerHistory: args.strictIssuerHistory,
675
1304
  skipIssuerHistory: args.noIssuerHistory,
676
1305
  canonicalPreimage,
1306
+ salt: args.salt,
1307
+ open,
1308
+ head: offlineWitness?.head ?? undefined,
1309
+ inclusion: offlineWitness?.inclusion ?? undefined,
1310
+ carry,
677
1311
  });
678
1312
  if (args.json) {
679
1313
  console.log(JSON.stringify(result, null, 2));
@@ -746,6 +1380,8 @@ async function main() {
746
1380
  }
747
1381
  }
748
1382
  console.log('');
1383
+ printUnsigned(result.unsigned_fields);
1384
+ console.log('');
749
1385
  console.log(` Nullifier`);
750
1386
  console.log(` ─────────────────────────────────────────────────`);
751
1387
  if (result.nullifier.nullifier) {
@@ -771,6 +1407,18 @@ async function main() {
771
1407
  else if (result.issuer_history.warning) {
772
1408
  console.log(` Issuer history: ${result.issuer_history.warning}`);
773
1409
  }
1410
+ if (result.carry_forward?.status === 'VERIFIED_CARRIED_FORWARD') {
1411
+ const carried = result.carry_forward;
1412
+ console.log(` Log`);
1413
+ console.log(` ─────────────────────────────────────────────────`);
1414
+ console.log(` Status: ${carried.status}`);
1415
+ console.log(` Issued at: ${carried.issued_at}`);
1416
+ console.log(` Issued epoch: ${carried.issued_epoch}`);
1417
+ console.log(` Logged at: ${carried.logged_at}`);
1418
+ console.log(` Logged epoch: ${carried.logged_epoch}`);
1419
+ console.log(` Leaf index: ${carried.leaf_index}`);
1420
+ console.log('');
1421
+ }
774
1422
  console.log(` Overall: ${result.overall.toUpperCase()}`);
775
1423
  if (result.errors.length > 0) {
776
1424
  console.log(` Errors: ${result.errors.join(', ')}`);
@@ -779,6 +1427,7 @@ async function main() {
779
1427
  }
780
1428
  switch (result.overall) {
781
1429
  case 'verified':
1430
+ case 'verified_carried_forward':
782
1431
  return 0;
783
1432
  case 'failed':
784
1433
  return 1;