@xfuel/verify 0.3.0 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -16
- package/dist/anchor-trust.d.ts +129 -0
- package/dist/anchor-trust.d.ts.map +1 -0
- package/dist/anchor-trust.js +395 -0
- package/dist/anchor-trust.js.map +1 -0
- package/dist/anchor-witness.d.ts +134 -7
- package/dist/anchor-witness.d.ts.map +1 -1
- package/dist/anchor-witness.js +497 -44
- package/dist/anchor-witness.js.map +1 -1
- package/dist/carry-forward.d.ts +79 -0
- package/dist/carry-forward.d.ts.map +1 -0
- package/dist/carry-forward.js +184 -0
- package/dist/carry-forward.js.map +1 -0
- package/dist/cli.js +674 -30
- package/dist/cli.js.map +1 -1
- package/dist/epoch.d.ts +143 -0
- package/dist/epoch.d.ts.map +1 -0
- package/dist/epoch.js +352 -0
- package/dist/epoch.js.map +1 -0
- package/dist/index.d.ts +86 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +395 -118
- package/dist/index.js.map +1 -1
- package/dist/issuer-history.d.ts +19 -0
- package/dist/issuer-history.d.ts.map +1 -1
- package/dist/issuer-history.js +131 -75
- package/dist/issuer-history.js.map +1 -1
- package/dist/issuer-pin.d.ts +126 -0
- package/dist/issuer-pin.d.ts.map +1 -0
- package/dist/issuer-pin.js +567 -0
- package/dist/issuer-pin.js.map +1 -0
- package/dist/preimage.d.ts +29 -0
- package/dist/preimage.d.ts.map +1 -1
- package/dist/preimage.js +162 -2
- package/dist/preimage.js.map +1 -1
- package/dist/receipt-lane.d.ts +1 -0
- package/dist/receipt-lane.d.ts.map +1 -1
- package/dist/receipt-lane.js +5 -2
- package/dist/receipt-lane.js.map +1 -1
- package/dist/refusal.d.ts +5 -2
- package/dist/refusal.d.ts.map +1 -1
- package/dist/refusal.js +28 -6
- package/dist/refusal.js.map +1 -1
- package/dist/request-binding.d.ts +7 -0
- package/dist/request-binding.d.ts.map +1 -0
- package/dist/request-binding.js +26 -0
- package/dist/request-binding.js.map +1 -0
- package/dist/row-hash.d.ts +22 -0
- package/dist/row-hash.d.ts.map +1 -0
- package/dist/row-hash.js +26 -0
- package/dist/row-hash.js.map +1 -0
- package/dist/v11-receipt.d.ts +21 -0
- package/dist/v11-receipt.d.ts.map +1 -0
- package/dist/v11-receipt.js +351 -0
- package/dist/v11-receipt.js.map +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -17,12 +17,17 @@
|
|
|
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");
|
|
24
|
+
const jws_js_1 = require("./jws.js");
|
|
23
25
|
const preimage_js_1 = require("./preimage.js");
|
|
24
26
|
const issuer_history_js_1 = require("./issuer-history.js");
|
|
25
27
|
const canonical_preimage_js_1 = require("./canonical-preimage.js");
|
|
28
|
+
const v11_receipt_js_1 = require("./v11-receipt.js");
|
|
29
|
+
const issuer_pin_js_1 = require("./issuer-pin.js");
|
|
30
|
+
const anchor_trust_js_1 = require("./anchor-trust.js");
|
|
26
31
|
const HELP = `
|
|
27
32
|
xfuel-verify — Offline verification for Chit402 receipts
|
|
28
33
|
|
|
@@ -51,10 +56,35 @@ Options:
|
|
|
51
56
|
--check-payer Query Base or Solana RPC. Base confirms payer, payee, asset, amount
|
|
52
57
|
--solana-rpc <url> Solana RPC URL (default: https://api.mainnet-beta.solana.com or SOLANA_RPC_URL)
|
|
53
58
|
--rpc <url> Base RPC URL (default: https://mainnet.base.org)
|
|
59
|
+
--version Print @xfuel/verify version and exit
|
|
54
60
|
--rpc With a receipt, an inclusion proof, and a tree head: check the
|
|
55
|
-
leaf, then the Solana memo and the Base calldata for that root
|
|
61
|
+
leaf, then the Solana memo and the Base calldata for that root.
|
|
62
|
+
The tree head must carry a valid ES256 issuer signature, and
|
|
63
|
+
the anchor wallets must match the signed head.
|
|
64
|
+
A v2 head also needs the signed epoch record.
|
|
65
|
+
A tree-head file with --rpc enters this mode.
|
|
66
|
+
The same files without --rpc check the head signature and the
|
|
67
|
+
index/size-bound inclusion offline. They do not call chain RPC.
|
|
68
|
+
--epoch-record <f> Signed epoch record JSON. Skips the network fetch.
|
|
69
|
+
--epoch-url <url> GET this epoch record. Default: the receipt verify_url
|
|
70
|
+
origin plus /v1/receipts/tree/epoch
|
|
56
71
|
--inclusion <file> Inclusion proof JSON (chit402.inclusion.v1)
|
|
57
|
-
--head <file> Signed tree head JSON (chit402.tree_head.v1)
|
|
72
|
+
--head <file> Signed tree head JSON (chit402.tree_head.v1 or v2)
|
|
73
|
+
--anchor-wallets-file <path>
|
|
74
|
+
Signed chit402.anchor_wallets.v1 document. Wallets in a
|
|
75
|
+
verified document are added to the package pin. A head
|
|
76
|
+
that names any other wallet fails.
|
|
77
|
+
--carry-old-head <file>
|
|
78
|
+
--carry-old-inclusion <file>
|
|
79
|
+
--carry-head <file>
|
|
80
|
+
--carry-inclusion <file>
|
|
81
|
+
Earlier signed head, its inclusion proof, the current
|
|
82
|
+
signed head, and the current inclusion proof. Repeat
|
|
83
|
+
--carry-inclusion for each current index of the leaf.
|
|
84
|
+
The issued time and epoch are the earlier head's signed
|
|
85
|
+
claims. The logged time, epoch, and leaf index are the
|
|
86
|
+
current head's signed claims. A receipt in the current
|
|
87
|
+
epoch does not use these flags.
|
|
58
88
|
--json Output JSON instead of human-readable
|
|
59
89
|
--quiet Only output errors
|
|
60
90
|
--strict-issuer-history
|
|
@@ -78,15 +108,24 @@ Trust:
|
|
|
78
108
|
issuer_jwk whose RFC 7638 thumbprint equals a pinned trusted kid. The
|
|
79
109
|
embedded key alone is not a trust root. Untrusted keys report "key untrusted".
|
|
80
110
|
|
|
81
|
-
Amount, payer, payee, and tx are read from verified signed claims.
|
|
82
|
-
with the
|
|
111
|
+
Amount, payer, payee, and tx are read from verified signed claims. Every
|
|
112
|
+
signed field is compared with the outer receipt at the same path. A mismatch
|
|
113
|
+
fails. Outer fields the signature does not cover are printed as UNVERIFIED.
|
|
114
|
+
The outer document schema is not a copy of the JWS schema. Log mode (a
|
|
115
|
+
receipt, an inclusion proof, and a tree head) runs these receipt checks too.
|
|
83
116
|
|
|
84
117
|
Network behavior:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- --
|
|
88
|
-
|
|
89
|
-
-
|
|
118
|
+
JWKS, nullifiers, and anchors are not fetched unless you pass a flag.
|
|
119
|
+
Issuer history is fetched by default when the receipt names a history URL
|
|
120
|
+
and neither --issuer-history-file nor --no-issuer-history is set. A payload
|
|
121
|
+
v10 receipt fails (exit 1) when that fetch cannot run. Offline mode is
|
|
122
|
+
--issuer-history-file <snapshot>, or --no-issuer-history to skip the window.
|
|
123
|
+
Other network uses:
|
|
124
|
+
- --fetch-jwks or --jwks-url
|
|
125
|
+
- --check-nullifier (Base RPC)
|
|
126
|
+
- --check-payer (Base or Solana RPC)
|
|
127
|
+
- --rpc with a receipt, an inclusion proof, and a tree head
|
|
128
|
+
- a v2 head, which fetches GET /v1/receipts/tree/epoch unless --epoch-record
|
|
90
129
|
|
|
91
130
|
Solana payer verify uses SOLANA_RPC_URL when set, else the public mainnet RPC.
|
|
92
131
|
|
|
@@ -94,11 +133,34 @@ Anchored root:
|
|
|
94
133
|
xfuel-verify receipt.json inclusion.json head.json --rpc
|
|
95
134
|
xfuel-verify receipt.json --inclusion inclusion.json --head head.json --rpc https://mainnet.base.org
|
|
96
135
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
136
|
+
Also runs the single-receipt checks (issuer signature and signed-versus-outer
|
|
137
|
+
comparison). Those must pass before Overall can be VERIFIED. Then it checks
|
|
138
|
+
the tree head's ES256 issuer signature (production pin, issuer
|
|
139
|
+
history, or JWKS, same rules as a receipt). The signed root, size, epoch,
|
|
140
|
+
and anchors must match the head. Then it checks Merkle inclusion, requires
|
|
141
|
+
the Solana memo and fee payer, and requires the Base calldata and sender.
|
|
142
|
+
The sender and fee payer must be on the issuer anchor-wallet list (the
|
|
143
|
+
package pin, plus a verified /.well-known/anchor-wallets.json or
|
|
144
|
+
issuer-history anchor_wallets list). The on-chain issuer-root registry and
|
|
145
|
+
the DNS anchor are not read. Prints what this proves and what it does not
|
|
146
|
+
prove. Exit 0 when the receipt checks pass and both chains match, 2 when the
|
|
147
|
+
leaf is included but an anchor is still pending, or when the leaf is past
|
|
148
|
+
the anchored tree (PENDING, not a tamper failure), and the receipt checks
|
|
149
|
+
did not fail, 1 when a check fails. A pending leaf must sit at an index
|
|
150
|
+
at or past the anchored size, and PENDING applies only when the receipt
|
|
151
|
+
itself verifies. A failed signature, field, or preimage is a failure.
|
|
152
|
+
With --rpc, a claimed anchor must name a Solana fee payer that is on the
|
|
153
|
+
anchor-wallet list, and the head root must equal the newest memo that
|
|
154
|
+
payer actually paid. A memo from any other payer is ignored.
|
|
155
|
+
|
|
156
|
+
A receipt or head with issuer_key_pin is a pinned era. Pass the pin file
|
|
157
|
+
and its commit SHA plus SHA-256. The verifier does not fetch a branch.
|
|
158
|
+
--issuer-pin <file> --issuer-pin-commit <40-hex> --issuer-pin-sha256 <64-hex>
|
|
159
|
+
Optional with that pin: --issuer-pin-sig, --issuer-witnesses,
|
|
160
|
+
--issuer-prior-pin, --issuer-prior-commit, --issuer-prior-sha256, --issuer-control.
|
|
161
|
+
Those flags do not start the check on their own. A prior pin does not
|
|
162
|
+
replace the published specimen key. Sepolia only (eip155:84532).
|
|
163
|
+
See docs/product/issuer-key-pin.md.
|
|
102
164
|
|
|
103
165
|
Receipt lane (unsigned, beside book_seq):
|
|
104
166
|
settled_by is observed_transfer when the USDC transfer was checked on Base
|
|
@@ -116,8 +178,8 @@ Receipt lane (unsigned, beside book_seq):
|
|
|
116
178
|
88201, 88403, and 88596).
|
|
117
179
|
|
|
118
180
|
Examples:
|
|
119
|
-
#
|
|
120
|
-
xfuel-verify my-receipt.json
|
|
181
|
+
# Skip the issuer-history fetch. A v10 receipt otherwise needs that network call.
|
|
182
|
+
xfuel-verify my-receipt.json --no-issuer-history
|
|
121
183
|
|
|
122
184
|
# Verify issuer signature with JWKS file
|
|
123
185
|
xfuel-verify my-receipt.json --jwks-file issuer-jwks.json
|
|
@@ -144,6 +206,12 @@ function parseArgs(args) {
|
|
|
144
206
|
anchorFlag: false,
|
|
145
207
|
inclusionFile: null,
|
|
146
208
|
headFile: null,
|
|
209
|
+
epochRecordFile: null,
|
|
210
|
+
epochUrl: null,
|
|
211
|
+
carryOldHeadFile: null,
|
|
212
|
+
carryOldInclusionFile: null,
|
|
213
|
+
carryHeadFile: null,
|
|
214
|
+
carryInclusionFiles: [],
|
|
147
215
|
positionals: [],
|
|
148
216
|
json: false,
|
|
149
217
|
quiet: false,
|
|
@@ -153,10 +221,26 @@ function parseArgs(args) {
|
|
|
153
221
|
canonicalPreimageFile: null,
|
|
154
222
|
noIssuerHistory: false,
|
|
155
223
|
noPreimage: false,
|
|
224
|
+
issuerPinFile: null,
|
|
225
|
+
issuerPinCommit: null,
|
|
226
|
+
issuerPinSha256: null,
|
|
227
|
+
issuerPinSigFile: null,
|
|
228
|
+
issuerWitnessesFile: null,
|
|
229
|
+
issuerPriorPinFile: null,
|
|
230
|
+
issuerPriorCommit: null,
|
|
231
|
+
issuerPriorSha256: null,
|
|
232
|
+
issuerControlFile: null,
|
|
233
|
+
version: false,
|
|
234
|
+
anchorWalletsFile: null,
|
|
235
|
+
salt: null,
|
|
236
|
+
open: [],
|
|
156
237
|
};
|
|
157
238
|
for (let i = 0; i < args.length; i++) {
|
|
158
239
|
const arg = args[i];
|
|
159
|
-
if (arg === '--
|
|
240
|
+
if (arg === '--version' || arg === '-V') {
|
|
241
|
+
result.version = true;
|
|
242
|
+
}
|
|
243
|
+
else if (arg === '--help' || arg === '-h') {
|
|
160
244
|
result.help = true;
|
|
161
245
|
}
|
|
162
246
|
else if (arg === '--check-nullifier') {
|
|
@@ -195,6 +279,24 @@ function parseArgs(args) {
|
|
|
195
279
|
else if (arg === '--head' && args[i + 1]) {
|
|
196
280
|
result.headFile = args[++i];
|
|
197
281
|
}
|
|
282
|
+
else if (arg === '--epoch-record' && args[i + 1]) {
|
|
283
|
+
result.epochRecordFile = args[++i];
|
|
284
|
+
}
|
|
285
|
+
else if (arg === '--epoch-url' && args[i + 1]) {
|
|
286
|
+
result.epochUrl = args[++i];
|
|
287
|
+
}
|
|
288
|
+
else if (arg === '--carry-old-head' && args[i + 1]) {
|
|
289
|
+
result.carryOldHeadFile = args[++i];
|
|
290
|
+
}
|
|
291
|
+
else if (arg === '--carry-old-inclusion' && args[i + 1]) {
|
|
292
|
+
result.carryOldInclusionFile = args[++i];
|
|
293
|
+
}
|
|
294
|
+
else if (arg === '--carry-head' && args[i + 1]) {
|
|
295
|
+
result.carryHeadFile = args[++i];
|
|
296
|
+
}
|
|
297
|
+
else if (arg === '--carry-inclusion' && args[i + 1]) {
|
|
298
|
+
result.carryInclusionFiles.push(args[++i]);
|
|
299
|
+
}
|
|
198
300
|
else if (arg === '--solana-rpc' && args[i + 1]) {
|
|
199
301
|
result.solanaRpcUrl = args[++i];
|
|
200
302
|
}
|
|
@@ -204,15 +306,51 @@ function parseArgs(args) {
|
|
|
204
306
|
else if (arg === '--issuer-history-file' && args[i + 1]) {
|
|
205
307
|
result.issuerHistoryFile = args[++i];
|
|
206
308
|
}
|
|
309
|
+
else if (arg === '--salt' && args[i + 1]) {
|
|
310
|
+
result.salt = args[++i];
|
|
311
|
+
}
|
|
312
|
+
else if (arg === '--open' && args[i + 1]) {
|
|
313
|
+
result.open.push(args[++i]);
|
|
314
|
+
}
|
|
207
315
|
else if (arg === '--canonical-preimage' && args[i + 1]) {
|
|
208
316
|
result.canonicalPreimageFile = args[++i];
|
|
209
317
|
}
|
|
210
318
|
else if (arg === '--no-issuer-history') {
|
|
211
319
|
result.noIssuerHistory = true;
|
|
212
320
|
}
|
|
321
|
+
else if (arg === '--anchor-wallets-file' && args[i + 1]) {
|
|
322
|
+
result.anchorWalletsFile = args[++i];
|
|
323
|
+
}
|
|
213
324
|
else if (arg === '--no-preimage') {
|
|
214
325
|
result.noPreimage = true;
|
|
215
326
|
}
|
|
327
|
+
else if (arg === '--issuer-pin' && args[i + 1]) {
|
|
328
|
+
result.issuerPinFile = args[++i];
|
|
329
|
+
}
|
|
330
|
+
else if (arg === '--issuer-pin-commit' && args[i + 1]) {
|
|
331
|
+
result.issuerPinCommit = args[++i];
|
|
332
|
+
}
|
|
333
|
+
else if (arg === '--issuer-pin-sha256' && args[i + 1]) {
|
|
334
|
+
result.issuerPinSha256 = args[++i];
|
|
335
|
+
}
|
|
336
|
+
else if (arg === '--issuer-pin-sig' && args[i + 1]) {
|
|
337
|
+
result.issuerPinSigFile = args[++i];
|
|
338
|
+
}
|
|
339
|
+
else if (arg === '--issuer-witnesses' && args[i + 1]) {
|
|
340
|
+
result.issuerWitnessesFile = args[++i];
|
|
341
|
+
}
|
|
342
|
+
else if (arg === '--issuer-prior-pin' && args[i + 1]) {
|
|
343
|
+
result.issuerPriorPinFile = args[++i];
|
|
344
|
+
}
|
|
345
|
+
else if (arg === '--issuer-prior-commit' && args[i + 1]) {
|
|
346
|
+
result.issuerPriorCommit = args[++i];
|
|
347
|
+
}
|
|
348
|
+
else if (arg === '--issuer-prior-sha256' && args[i + 1]) {
|
|
349
|
+
result.issuerPriorSha256 = args[++i];
|
|
350
|
+
}
|
|
351
|
+
else if (arg === '--issuer-control' && args[i + 1]) {
|
|
352
|
+
result.issuerControlFile = args[++i];
|
|
353
|
+
}
|
|
216
354
|
else if (arg === '--json') {
|
|
217
355
|
result.json = true;
|
|
218
356
|
}
|
|
@@ -250,6 +388,15 @@ function readJson(file) {
|
|
|
250
388
|
const content = file === '-' ? (0, node_fs_1.readFileSync)(0, 'utf8') : (0, node_fs_1.readFileSync)(file, 'utf8');
|
|
251
389
|
return JSON.parse(content);
|
|
252
390
|
}
|
|
391
|
+
function headStatus(file) {
|
|
392
|
+
try {
|
|
393
|
+
const doc = readJson(file);
|
|
394
|
+
return typeof doc?.status === 'string' ? doc.status : null;
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
253
400
|
function printLane(lane) {
|
|
254
401
|
const bit = (value) => (value == null ? 'unknown' : (value ? 'true' : 'false'));
|
|
255
402
|
console.log(` Receipt lane (unsigned)`);
|
|
@@ -282,7 +429,18 @@ function printAnchor(result, json, quiet) {
|
|
|
282
429
|
console.log(' Anchored receipt root');
|
|
283
430
|
console.log(' ─────────────────────────────────────────────────');
|
|
284
431
|
console.log(` Root: ${result.root || '—'}`);
|
|
285
|
-
console.log(`
|
|
432
|
+
console.log(` Head signature:${result.head_signature.valid ? ' ✓ YES' : ' ✗ NO'}${result.head_signature.trust ? ` (${result.head_signature.trust})` : ''}`);
|
|
433
|
+
if (result.head_signature.kid)
|
|
434
|
+
console.log(` Head kid: ${result.head_signature.kid}`);
|
|
435
|
+
if (result.head_signature.message)
|
|
436
|
+
console.log(` Head reason: ${result.head_signature.message}`);
|
|
437
|
+
const unlogged = result.inclusion.unlogged_reason ? ` unlogged:${result.inclusion.unlogged_reason}` : '';
|
|
438
|
+
if (result.overall === 'pending') {
|
|
439
|
+
console.log(` Inclusion: PENDING${result.inclusion.reason ? ` (${result.inclusion.reason})` : ''}`);
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
console.log(` Inclusion: ${mark(result.inclusion.valid)}${result.inclusion.reason ? ` (${result.inclusion.reason})` : ''}${unlogged}`);
|
|
443
|
+
}
|
|
286
444
|
console.log(` Leaf source: ${result.inclusion.leaf_source}`);
|
|
287
445
|
console.log(` Solana: ${result.solana.checked ? mark(result.solana.valid) : (result.solana.reason || 'not checked')}`);
|
|
288
446
|
if (result.solana.signature)
|
|
@@ -291,11 +449,18 @@ function printAnchor(result, json, quiet) {
|
|
|
291
449
|
console.log(` Slot: ${result.solana.slot}`);
|
|
292
450
|
if (result.solana.cluster)
|
|
293
451
|
console.log(` Cluster: ${result.solana.cluster}`);
|
|
452
|
+
if (result.solana.fee_payer)
|
|
453
|
+
console.log(` Fee payer: ${result.solana.fee_payer}`);
|
|
294
454
|
if (result.solana.memo)
|
|
295
455
|
console.log(` Memo: ${result.solana.memo}`);
|
|
296
456
|
if (result.solana.reason && !result.solana.valid)
|
|
297
457
|
console.log(` Solana reason: ${result.solana.reason}`);
|
|
458
|
+
if (result.issuer_pin?.checked) {
|
|
459
|
+
console.log(` Issuer pin: ${result.issuer_pin.ok ? '✓ YES' : '✗ NO'}${result.issuer_pin.code ? ` (${result.issuer_pin.code})` : ''}`);
|
|
460
|
+
}
|
|
298
461
|
console.log(` Base: ${result.base.checked ? mark(result.base.valid) : (result.base.reason || 'not checked')}`);
|
|
462
|
+
if (result.base.from)
|
|
463
|
+
console.log(` Base from: ${result.base.from}`);
|
|
299
464
|
if (result.base.tx)
|
|
300
465
|
console.log(` Base tx: ${result.base.tx}`);
|
|
301
466
|
if (result.base.chain_id != null)
|
|
@@ -313,15 +478,207 @@ function printAnchor(result, json, quiet) {
|
|
|
313
478
|
for (const line of result.does_not_prove)
|
|
314
479
|
console.log(` ${line}`);
|
|
315
480
|
console.log('');
|
|
481
|
+
if (result.overall === 'pending') {
|
|
482
|
+
console.log(` Anchored size: ${result.inclusion.anchored_tree_size ?? '—'}`);
|
|
483
|
+
console.log(` Live size: ${result.inclusion.live_tree_size ?? '—'}`);
|
|
484
|
+
}
|
|
316
485
|
console.log(` Overall: ${result.overall.toUpperCase()}`);
|
|
317
486
|
if (result.errors.length > 0)
|
|
318
487
|
console.log(` Errors: ${result.errors.join(', ')}`);
|
|
488
|
+
if (result.errors.includes(anchor_trust_js_1.LEGACY_HEAD_UNPINNED_SIGNER)) {
|
|
489
|
+
console.log(` ${anchor_trust_js_1.HEAD_TRUST_MESSAGES[anchor_trust_js_1.LEGACY_HEAD_UNPINNED_SIGNER]}`);
|
|
490
|
+
}
|
|
319
491
|
console.log('');
|
|
320
492
|
}
|
|
493
|
+
function headNeedsEpochRecord(head) {
|
|
494
|
+
const schema = head.schema;
|
|
495
|
+
const version = head.payload_version;
|
|
496
|
+
return head.epoch != null || schema === 'chit402.tree_head.v2' || version === 2;
|
|
497
|
+
}
|
|
498
|
+
function epochUrlFromReceipt(receipt) {
|
|
499
|
+
const raw = receipt?.verify_url;
|
|
500
|
+
if (!raw)
|
|
501
|
+
return null;
|
|
502
|
+
try {
|
|
503
|
+
return `${new URL(raw).origin}/v1/receipts/tree/epoch`;
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async function loadEpochRecord(args, receipt) {
|
|
510
|
+
if (args.epochRecordFile) {
|
|
511
|
+
return JSON.parse((0, node_fs_1.readFileSync)(args.epochRecordFile, 'utf8'));
|
|
512
|
+
}
|
|
513
|
+
const url = args.epochUrl || epochUrlFromReceipt(receipt);
|
|
514
|
+
if (!url)
|
|
515
|
+
return null;
|
|
516
|
+
const res = await fetch(url);
|
|
517
|
+
if (!res.ok)
|
|
518
|
+
return null;
|
|
519
|
+
const body = await res.json();
|
|
520
|
+
if (!body?.issuer_signature?.jws)
|
|
521
|
+
return null;
|
|
522
|
+
return body;
|
|
523
|
+
}
|
|
524
|
+
function epochSignatureOk(record, jws, jwks, trustedKids) {
|
|
525
|
+
const sig = record.issuer_signature;
|
|
526
|
+
const embedded = sig && 'issuer_jwk' in sig
|
|
527
|
+
? sig.issuer_jwk
|
|
528
|
+
: undefined;
|
|
529
|
+
const kid = (0, jws_js_1.readJwsHeader)(jws)?.kid;
|
|
530
|
+
const keys = [];
|
|
531
|
+
for (const key of jwks?.keys || []) {
|
|
532
|
+
if (!key || key.kty !== 'EC')
|
|
533
|
+
continue;
|
|
534
|
+
const jwk = key;
|
|
535
|
+
if (kid && jwk.kid !== kid)
|
|
536
|
+
continue;
|
|
537
|
+
keys.push(jwk);
|
|
538
|
+
}
|
|
539
|
+
if (embedded && trustedKids?.includes((0, jws_js_1.jwkThumbprint)(embedded)))
|
|
540
|
+
keys.push(embedded);
|
|
541
|
+
for (const key of keys) {
|
|
542
|
+
const verified = (0, jws_js_1.verifyIssuerJws)(jws, key);
|
|
543
|
+
if (!verified.valid || !verified.payload)
|
|
544
|
+
continue;
|
|
545
|
+
const payload = verified.payload;
|
|
546
|
+
if (JSON.stringify(payload.epochs ?? null) !== JSON.stringify(record.epochs ?? null))
|
|
547
|
+
return false;
|
|
548
|
+
if (JSON.stringify(payload.orphans ?? []) !== JSON.stringify(record.orphans ?? []))
|
|
549
|
+
return false;
|
|
550
|
+
const payloadUnlogged = payload.unlogged === undefined ? null : payload.unlogged;
|
|
551
|
+
const recordUnlogged = record.unlogged === undefined ? null : record.unlogged;
|
|
552
|
+
if (JSON.stringify(payloadUnlogged) !== JSON.stringify(recordUnlogged))
|
|
553
|
+
return false;
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
function packageVersion() {
|
|
559
|
+
const require = (0, node_module_1.createRequire)(__filename);
|
|
560
|
+
const pkg = require('../package.json');
|
|
561
|
+
return pkg.version || '0.0.0';
|
|
562
|
+
}
|
|
563
|
+
function looksLikeTreeHead(value) {
|
|
564
|
+
if (!value || typeof value !== 'object')
|
|
565
|
+
return false;
|
|
566
|
+
const doc = value;
|
|
567
|
+
if (doc.status === 'not_yet_published')
|
|
568
|
+
return true;
|
|
569
|
+
if (typeof doc.schema === 'string' && doc.schema.startsWith('chit402.tree_head.'))
|
|
570
|
+
return true;
|
|
571
|
+
return Boolean(doc.anchors && doc.root && !doc.task_id);
|
|
572
|
+
}
|
|
573
|
+
function readOfflineWitness(args) {
|
|
574
|
+
let head = null;
|
|
575
|
+
let inclusion = null;
|
|
576
|
+
for (const file of args.positionals.slice(1)) {
|
|
577
|
+
let doc;
|
|
578
|
+
try {
|
|
579
|
+
doc = readJson(file);
|
|
580
|
+
}
|
|
581
|
+
catch (err) {
|
|
582
|
+
throw new Error(`Error reading ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
583
|
+
}
|
|
584
|
+
const row = doc;
|
|
585
|
+
if (looksLikeTreeHead(doc))
|
|
586
|
+
head = doc;
|
|
587
|
+
else if (doc && typeof doc === 'object' && (Array.isArray(row.proof) || row.leaf_index != null)) {
|
|
588
|
+
inclusion = doc;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return { head, inclusion };
|
|
592
|
+
}
|
|
593
|
+
function fileLooksLikeHead(file) {
|
|
594
|
+
if (!file)
|
|
595
|
+
return false;
|
|
596
|
+
try {
|
|
597
|
+
return looksLikeTreeHead(readJson(file));
|
|
598
|
+
}
|
|
599
|
+
catch {
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function historyHostAllowed(url) {
|
|
604
|
+
try {
|
|
605
|
+
const parsed = new URL(url);
|
|
606
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
|
|
607
|
+
return false;
|
|
608
|
+
if (parsed.protocol === 'http:' && parsed.hostname !== '127.0.0.1' && parsed.hostname !== 'localhost')
|
|
609
|
+
return false;
|
|
610
|
+
if (parsed.protocol === 'https:') {
|
|
611
|
+
return index_js_1.DEFAULT_TRUSTED_JWKS_HOSTS.some((host) => host.toLowerCase() === parsed.hostname.toLowerCase());
|
|
612
|
+
}
|
|
613
|
+
return true;
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
return false;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
async function loadAnchorIssuerHistory(args, receipt) {
|
|
620
|
+
if (args.issuerHistoryFile) {
|
|
621
|
+
return JSON.parse((0, node_fs_1.readFileSync)(args.issuerHistoryFile, 'utf8'));
|
|
622
|
+
}
|
|
623
|
+
if (args.noIssuerHistory)
|
|
624
|
+
return null;
|
|
625
|
+
const url = (0, issuer_history_js_1.historyUrlFromReceipt)(receipt);
|
|
626
|
+
if (!url || !historyHostAllowed(url))
|
|
627
|
+
return null;
|
|
628
|
+
try {
|
|
629
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(4000) });
|
|
630
|
+
if (!res.ok)
|
|
631
|
+
return null;
|
|
632
|
+
return await res.json();
|
|
633
|
+
}
|
|
634
|
+
catch {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
async function loadAnchorWalletDocument(args, receipt) {
|
|
639
|
+
if (args.anchorWalletsFile) {
|
|
640
|
+
return JSON.parse((0, node_fs_1.readFileSync)(args.anchorWalletsFile, 'utf8'));
|
|
641
|
+
}
|
|
642
|
+
if (args.noIssuerHistory)
|
|
643
|
+
return null;
|
|
644
|
+
const historyUrl = (0, issuer_history_js_1.historyUrlFromReceipt)(receipt);
|
|
645
|
+
const epochOrigin = (() => {
|
|
646
|
+
if (!args.epochUrl)
|
|
647
|
+
return null;
|
|
648
|
+
try {
|
|
649
|
+
return new URL(args.epochUrl).origin;
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
})();
|
|
655
|
+
const origin = epochOrigin || (historyUrl ? new URL(historyUrl).origin : null);
|
|
656
|
+
if (!origin)
|
|
657
|
+
return null;
|
|
658
|
+
const url = `${origin}/.well-known/anchor-wallets.json`;
|
|
659
|
+
if (!historyHostAllowed(url))
|
|
660
|
+
return null;
|
|
661
|
+
try {
|
|
662
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(4000) });
|
|
663
|
+
if (res.status === 404)
|
|
664
|
+
return null;
|
|
665
|
+
if (!res.ok)
|
|
666
|
+
return null;
|
|
667
|
+
return await res.json();
|
|
668
|
+
}
|
|
669
|
+
catch {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
321
673
|
async function runAnchor(args) {
|
|
322
674
|
const receiptPath = args.file;
|
|
323
675
|
const inclusionPath = args.inclusionFile || args.positionals[1] || null;
|
|
324
676
|
const headPath = args.headFile || (args.inclusionFile ? null : args.positionals[2]) || null;
|
|
677
|
+
const headCandidates = [args.headFile, headPath, ...args.positionals];
|
|
678
|
+
if (headCandidates.some((file) => file && headStatus(file) === 'not_yet_published')) {
|
|
679
|
+
console.error('Tree head is not_yet_published.');
|
|
680
|
+
return 3;
|
|
681
|
+
}
|
|
325
682
|
if (!receiptPath || !inclusionPath || !headPath) {
|
|
326
683
|
console.error('Anchor check needs a receipt, an inclusion proof, and a tree head.');
|
|
327
684
|
console.error(' xfuel-verify receipt.json inclusion.json head.json --rpc');
|
|
@@ -339,29 +696,198 @@ async function runAnchor(args) {
|
|
|
339
696
|
console.error(`Error reading anchor inputs: ${err instanceof Error ? err.message : String(err)}`);
|
|
340
697
|
return 3;
|
|
341
698
|
}
|
|
699
|
+
let fileJwks;
|
|
700
|
+
if (args.jwksFile) {
|
|
701
|
+
try {
|
|
702
|
+
fileJwks = JSON.parse((0, node_fs_1.readFileSync)(args.jwksFile, 'utf8'));
|
|
703
|
+
}
|
|
704
|
+
catch (err) {
|
|
705
|
+
console.error(`Error reading JWKS file: ${err instanceof Error ? err.message : String(err)}`);
|
|
706
|
+
return 3;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
const loaded = await (0, index_js_1.loadIssuerJwks)(receipt, {
|
|
710
|
+
jwks: fileJwks,
|
|
711
|
+
jwksUri: args.jwksUrl || undefined,
|
|
712
|
+
fetchJwks: args.fetchJwks,
|
|
713
|
+
});
|
|
714
|
+
const trustedKids = args.noTrustedKid
|
|
715
|
+
? []
|
|
716
|
+
: (args.trustedKids ?? [...index_js_1.DEFAULT_TRUSTED_ISSUER_KIDS]);
|
|
717
|
+
let issuerHistory = null;
|
|
718
|
+
let anchorWalletDocument = null;
|
|
719
|
+
try {
|
|
720
|
+
issuerHistory = await loadAnchorIssuerHistory(args, receipt);
|
|
721
|
+
anchorWalletDocument = await loadAnchorWalletDocument(args, receipt);
|
|
722
|
+
}
|
|
723
|
+
catch (err) {
|
|
724
|
+
console.error(`Error reading issuer trust files: ${err instanceof Error ? err.message : String(err)}`);
|
|
725
|
+
return 3;
|
|
726
|
+
}
|
|
727
|
+
let epochRecord = null;
|
|
728
|
+
let verifyEpochSignature;
|
|
729
|
+
if (headNeedsEpochRecord(head)) {
|
|
730
|
+
try {
|
|
731
|
+
epochRecord = await loadEpochRecord(args, receipt);
|
|
732
|
+
}
|
|
733
|
+
catch {
|
|
734
|
+
epochRecord = null;
|
|
735
|
+
}
|
|
736
|
+
if (epochRecord?.issuer_signature?.jws) {
|
|
737
|
+
verifyEpochSignature = (jws) => epochSignatureOk(epochRecord, jws, loaded.jwks, trustedKids);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
let issuerPin;
|
|
741
|
+
const pinClaim = (0, issuer_pin_js_1.readIssuerPinClaim)(receipt).claimed || (0, issuer_pin_js_1.readIssuerPinClaim)(head).claimed;
|
|
742
|
+
const pinRequested = Boolean(args.issuerPinFile) || pinClaim;
|
|
743
|
+
if (pinRequested) {
|
|
744
|
+
try {
|
|
745
|
+
const claimRef = (0, issuer_pin_js_1.readIssuerPinClaim)(receipt).ref || (0, issuer_pin_js_1.readIssuerPinClaim)(head).ref;
|
|
746
|
+
const ref = (args.issuerPinCommit || args.issuerPinSha256 || claimRef)
|
|
747
|
+
? {
|
|
748
|
+
commit: args.issuerPinCommit || claimRef?.commit || '',
|
|
749
|
+
path: claimRef?.path || 'docs/well-known/issuer-key.json',
|
|
750
|
+
sha256: (args.issuerPinSha256 || claimRef?.sha256 || '').toLowerCase(),
|
|
751
|
+
}
|
|
752
|
+
: null;
|
|
753
|
+
let witnesses;
|
|
754
|
+
if (args.issuerWitnessesFile)
|
|
755
|
+
witnesses = JSON.parse((0, node_fs_1.readFileSync)(args.issuerWitnessesFile, 'utf8'));
|
|
756
|
+
const priorRef = args.issuerPriorPinFile
|
|
757
|
+
? {
|
|
758
|
+
commit: args.issuerPriorCommit || '',
|
|
759
|
+
path: 'docs/well-known/issuer-key.json',
|
|
760
|
+
sha256: (args.issuerPriorSha256 || '').toLowerCase(),
|
|
761
|
+
}
|
|
762
|
+
: null;
|
|
763
|
+
issuerPin = {
|
|
764
|
+
pinBytes: args.issuerPinFile ? (0, node_fs_1.readFileSync)(args.issuerPinFile, 'utf8') : null,
|
|
765
|
+
sigBytes: args.issuerPinSigFile ? (0, node_fs_1.readFileSync)(args.issuerPinSigFile, 'utf8') : null,
|
|
766
|
+
ref,
|
|
767
|
+
witnesses,
|
|
768
|
+
priorPinBytes: args.issuerPriorPinFile ? (0, node_fs_1.readFileSync)(args.issuerPriorPinFile, 'utf8') : null,
|
|
769
|
+
priorRef,
|
|
770
|
+
controlJws: args.issuerControlFile ? (0, node_fs_1.readFileSync)(args.issuerControlFile, 'utf8').trim() : null,
|
|
771
|
+
required: true,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
catch (err) {
|
|
775
|
+
console.error(`Error reading issuer pin: ${err instanceof Error ? err.message : String(err)}`);
|
|
776
|
+
return 3;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
342
779
|
const result = await (0, anchor_witness_js_1.verifyAnchoredRoot)({
|
|
343
780
|
receipt,
|
|
344
781
|
inclusion,
|
|
345
782
|
head,
|
|
346
783
|
baseRpcUrl: args.rpcUrl || undefined,
|
|
347
784
|
solanaRpcUrl: args.solanaRpcUrl || undefined,
|
|
785
|
+
checkNewestAnchor: args.sawRpc,
|
|
786
|
+
epochRecord,
|
|
787
|
+
verifyEpochSignature,
|
|
788
|
+
issuerPin,
|
|
789
|
+
jwks: loaded.jwks,
|
|
790
|
+
trustedKids,
|
|
791
|
+
issuerHistory,
|
|
792
|
+
strictIssuerHistory: args.strictIssuerHistory,
|
|
793
|
+
anchorWalletDocument: anchorWalletDocument,
|
|
348
794
|
});
|
|
349
|
-
|
|
350
|
-
|
|
795
|
+
let canonicalPreimage = null;
|
|
796
|
+
if (args.canonicalPreimageFile) {
|
|
797
|
+
canonicalPreimage = (0, node_fs_1.readFileSync)(args.canonicalPreimageFile, 'utf8');
|
|
798
|
+
}
|
|
799
|
+
const historyBytes = args.issuerHistoryFile ? (0, node_fs_1.readFileSync)(args.issuerHistoryFile, 'utf8') : null;
|
|
800
|
+
const verified = await (0, index_js_1.verifyReceipt)(receipt, {
|
|
801
|
+
jwks: loaded.jwks,
|
|
802
|
+
jwksUri: args.jwksUrl || undefined,
|
|
803
|
+
fetchJwks: args.fetchJwks,
|
|
804
|
+
trustedKids,
|
|
805
|
+
checkNullifier: args.checkNullifier,
|
|
806
|
+
checkPayer: args.checkPayer,
|
|
807
|
+
rpcUrl: args.rpcUrl || undefined,
|
|
808
|
+
solanaRpcUrl: args.solanaRpcUrl || undefined,
|
|
809
|
+
requirePreimages: !args.noPreimage,
|
|
810
|
+
issuerHistory: args.issuerHistoryFile ? issuerHistory : null,
|
|
811
|
+
issuerHistoryBytes: historyBytes,
|
|
812
|
+
fetchIssuerHistory: !args.noIssuerHistory && !args.issuerHistoryFile,
|
|
813
|
+
strictIssuerHistory: args.strictIssuerHistory,
|
|
814
|
+
skipIssuerHistory: args.noIssuerHistory,
|
|
815
|
+
canonicalPreimage,
|
|
816
|
+
head,
|
|
817
|
+
});
|
|
818
|
+
// Inclusion was already checked above. Passing it again re-derives the leaf
|
|
819
|
+
// from book_chain.row_hash only, which rejects a receipt that stores row_hash
|
|
820
|
+
// at the top level. Log mode still requires the receipt itself. A missing or
|
|
821
|
+
// stripped issuer signature, a partial check, or a signed-versus-outer
|
|
822
|
+
// mismatch fails the combined result. An inclusion match is not VERIFIED.
|
|
823
|
+
const issuerSig = receipt.issuer_signature;
|
|
824
|
+
const signaturePresent = Boolean(issuerSig?.jws || issuerSig?.value);
|
|
825
|
+
const receiptVerified = signaturePresent
|
|
826
|
+
&& verified.issuer_signature.valid === true
|
|
827
|
+
&& (verified.overall === 'verified' || verified.overall === 'verified_carried_forward')
|
|
828
|
+
&& verified.claim_mismatches.length === 0;
|
|
829
|
+
const receiptFailed = !receiptVerified;
|
|
830
|
+
const receiptErrors = [...verified.errors];
|
|
831
|
+
if (!signaturePresent) {
|
|
832
|
+
receiptErrors.push(verified.issuer_signature.reason || 'receipt_signature_missing');
|
|
833
|
+
}
|
|
834
|
+
else if (verified.overall === 'partial') {
|
|
835
|
+
receiptErrors.push(verified.issuer_signature.reason || 'receipt_check_partial');
|
|
836
|
+
}
|
|
837
|
+
const errors = receiptFailed
|
|
838
|
+
? [...result.errors, ...receiptErrors.filter((err) => !result.errors.includes(err))]
|
|
839
|
+
: result.errors;
|
|
840
|
+
const combined = {
|
|
841
|
+
...result,
|
|
842
|
+
errors,
|
|
843
|
+
overall: receiptFailed ? 'failed' : result.overall,
|
|
844
|
+
receipt_lane: verified.receipt_lane,
|
|
845
|
+
receipt_check: {
|
|
846
|
+
overall: result.overall === 'pending' && !receiptFailed ? 'pending' : verified.overall,
|
|
847
|
+
verified_scope: verified.verified_scope,
|
|
848
|
+
claim_mismatches: verified.claim_mismatches,
|
|
849
|
+
unsigned_fields: verified.unsigned_fields,
|
|
850
|
+
errors: verified.errors,
|
|
851
|
+
},
|
|
852
|
+
};
|
|
351
853
|
if (args.json) {
|
|
352
|
-
console.log(JSON.stringify(
|
|
854
|
+
console.log(JSON.stringify(combined, null, 2));
|
|
353
855
|
}
|
|
354
856
|
else {
|
|
355
|
-
printAnchor(
|
|
857
|
+
printAnchor(combined, false, args.quiet);
|
|
858
|
+
if (!args.quiet || receiptFailed) {
|
|
859
|
+
const anchorPending = result.overall === 'pending';
|
|
860
|
+
const receiptLabel = anchorPending && !receiptFailed
|
|
861
|
+
? 'PENDING'
|
|
862
|
+
: verified.overall.toUpperCase();
|
|
863
|
+
console.log(` Receipt checks: ${receiptLabel} (signed claims only)`);
|
|
864
|
+
if (verified.claim_mismatches.length > 0) {
|
|
865
|
+
for (const mismatch of verified.claim_mismatches) {
|
|
866
|
+
console.log(` ${mismatch.field}: outer ${mismatch.outer} ≠ signed ${mismatch.signed}`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (!(anchorPending && !receiptFailed))
|
|
870
|
+
printUnsigned(verified.unsigned_fields);
|
|
871
|
+
console.log('');
|
|
872
|
+
}
|
|
356
873
|
if (!args.quiet)
|
|
357
|
-
printLane(
|
|
874
|
+
printLane(verified.receipt_lane);
|
|
358
875
|
}
|
|
359
|
-
if (
|
|
876
|
+
if (combined.overall === 'verified')
|
|
360
877
|
return 0;
|
|
361
|
-
if (
|
|
878
|
+
if (combined.overall === 'partial' || combined.overall === 'pending')
|
|
362
879
|
return 2;
|
|
363
880
|
return 1;
|
|
364
881
|
}
|
|
882
|
+
function printUnsigned(fields) {
|
|
883
|
+
console.log(' Scope: signed claims only. Unsigned fields are UNVERIFIED.');
|
|
884
|
+
if (fields.length === 0)
|
|
885
|
+
return;
|
|
886
|
+
console.log(` Unsigned fields (${fields.length})`);
|
|
887
|
+
console.log(' ─────────────────────────────────────────────────');
|
|
888
|
+
for (const field of fields)
|
|
889
|
+
console.log(` UNVERIFIED ${field}`);
|
|
890
|
+
}
|
|
365
891
|
async function runRefusal(doc, args) {
|
|
366
892
|
const trustedKids = args.trustedKids ?? index_js_1.DEFAULT_TRUSTED_ISSUER_KIDS;
|
|
367
893
|
const result = (0, index_js_1.verifyRefusal)(doc, {
|
|
@@ -384,7 +910,7 @@ async function runRefusal(doc, args) {
|
|
|
384
910
|
issuer_history: result.issuer_history,
|
|
385
911
|
}) : null;
|
|
386
912
|
const history = args.skipIssuerHistory
|
|
387
|
-
? { checked: false, ok: true, unreachable: false, warning: null, reason: null, kid: result.kid ?? null }
|
|
913
|
+
? { checked: false, ok: true, unreachable: false, warning: null, reason: null, kid: result.kid ?? null, document: null, loaded: false }
|
|
388
914
|
: await (0, issuer_history_js_1.checkReceiptIssuerHistory)(doc, {
|
|
389
915
|
document: args.issuerHistory,
|
|
390
916
|
documentBytes: args.issuerHistoryBytes,
|
|
@@ -444,13 +970,30 @@ async function runRefusal(doc, args) {
|
|
|
444
970
|
}
|
|
445
971
|
async function main() {
|
|
446
972
|
const args = parseArgs(process.argv.slice(2));
|
|
447
|
-
|
|
973
|
+
if (args.version) {
|
|
974
|
+
console.log(`@xfuel/verify ${packageVersion()}`);
|
|
975
|
+
return 0;
|
|
976
|
+
}
|
|
977
|
+
let anchorMode = args.anchorFlag
|
|
448
978
|
|| Boolean(args.inclusionFile || args.headFile)
|
|
449
979
|
|| (args.positionals.length >= 3 && args.sawRpc);
|
|
980
|
+
if (!anchorMode && args.sawRpc && args.positionals.some((file) => fileLooksLikeHead(file))) {
|
|
981
|
+
anchorMode = true;
|
|
982
|
+
}
|
|
450
983
|
if (args.help) {
|
|
451
984
|
console.log(HELP);
|
|
452
985
|
return 0;
|
|
453
986
|
}
|
|
987
|
+
let offlineWitness = null;
|
|
988
|
+
if (!anchorMode) {
|
|
989
|
+
try {
|
|
990
|
+
offlineWitness = readOfflineWitness(args);
|
|
991
|
+
}
|
|
992
|
+
catch (err) {
|
|
993
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
994
|
+
return 3;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
454
997
|
if (anchorMode)
|
|
455
998
|
return runAnchor(args);
|
|
456
999
|
if (!args.file) {
|
|
@@ -465,7 +1008,19 @@ async function main() {
|
|
|
465
1008
|
console.error(`Error reading receipt: ${err instanceof Error ? err.message : String(err)}`);
|
|
466
1009
|
return 3;
|
|
467
1010
|
}
|
|
468
|
-
|
|
1011
|
+
const v11 = receipt.v === 11 || (receipt.issuer_signature?.jws
|
|
1012
|
+
? (() => {
|
|
1013
|
+
try {
|
|
1014
|
+
const part = String(receipt.issuer_signature.jws).split('.')[1];
|
|
1015
|
+
const payload = JSON.parse(Buffer.from(part, 'base64url').toString('utf8'));
|
|
1016
|
+
return payload?.v === 11;
|
|
1017
|
+
}
|
|
1018
|
+
catch {
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
})()
|
|
1022
|
+
: false);
|
|
1023
|
+
if (!receipt.task_id && !v11) {
|
|
469
1024
|
console.error('Invalid receipt: missing task_id');
|
|
470
1025
|
return 3;
|
|
471
1026
|
}
|
|
@@ -531,6 +1086,42 @@ async function main() {
|
|
|
531
1086
|
return 3;
|
|
532
1087
|
}
|
|
533
1088
|
}
|
|
1089
|
+
const carryAsked = args.carryOldHeadFile || args.carryOldInclusionFile
|
|
1090
|
+
|| args.carryHeadFile || args.carryInclusionFiles.length > 0;
|
|
1091
|
+
let carry = null;
|
|
1092
|
+
if (carryAsked) {
|
|
1093
|
+
try {
|
|
1094
|
+
carry = {
|
|
1095
|
+
oldHead: args.carryOldHeadFile ? readJson(args.carryOldHeadFile) : {},
|
|
1096
|
+
oldInclusion: args.carryOldInclusionFile ? readJson(args.carryOldInclusionFile) : { error: 'not_in_tree' },
|
|
1097
|
+
currentHead: args.carryHeadFile ? readJson(args.carryHeadFile) : {},
|
|
1098
|
+
currentInclusions: args.carryInclusionFiles.length > 0
|
|
1099
|
+
? args.carryInclusionFiles.map((file) => readJson(file))
|
|
1100
|
+
: [{ error: 'not_in_tree' }],
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
catch (err) {
|
|
1104
|
+
console.error(`Error reading carry-forward files: ${err instanceof Error ? err.message : String(err)}`);
|
|
1105
|
+
return 3;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
let open;
|
|
1109
|
+
if (args.open.length > 0) {
|
|
1110
|
+
open = {};
|
|
1111
|
+
try {
|
|
1112
|
+
for (const spec of args.open) {
|
|
1113
|
+
const opened = (0, v11_receipt_js_1.readOpenFile)(spec);
|
|
1114
|
+
if (opened.kind === 'output')
|
|
1115
|
+
open.output = opened.bytes;
|
|
1116
|
+
else
|
|
1117
|
+
open[opened.kind] = opened.text;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
catch (err) {
|
|
1121
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
1122
|
+
return 3;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
534
1125
|
const result = await (0, index_js_1.verifyReceipt)(receipt, {
|
|
535
1126
|
jwks,
|
|
536
1127
|
jwksUri: args.jwksUrl || undefined,
|
|
@@ -547,6 +1138,11 @@ async function main() {
|
|
|
547
1138
|
strictIssuerHistory: args.strictIssuerHistory,
|
|
548
1139
|
skipIssuerHistory: args.noIssuerHistory,
|
|
549
1140
|
canonicalPreimage,
|
|
1141
|
+
salt: args.salt,
|
|
1142
|
+
open,
|
|
1143
|
+
head: offlineWitness?.head ?? undefined,
|
|
1144
|
+
inclusion: offlineWitness?.inclusion ?? undefined,
|
|
1145
|
+
carry,
|
|
550
1146
|
});
|
|
551
1147
|
if (args.json) {
|
|
552
1148
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -619,6 +1215,8 @@ async function main() {
|
|
|
619
1215
|
}
|
|
620
1216
|
}
|
|
621
1217
|
console.log('');
|
|
1218
|
+
printUnsigned(result.unsigned_fields);
|
|
1219
|
+
console.log('');
|
|
622
1220
|
console.log(` Nullifier`);
|
|
623
1221
|
console.log(` ─────────────────────────────────────────────────`);
|
|
624
1222
|
if (result.nullifier.nullifier) {
|
|
@@ -644,6 +1242,18 @@ async function main() {
|
|
|
644
1242
|
else if (result.issuer_history.warning) {
|
|
645
1243
|
console.log(` Issuer history: ${result.issuer_history.warning}`);
|
|
646
1244
|
}
|
|
1245
|
+
if (result.carry_forward?.status === 'VERIFIED_CARRIED_FORWARD') {
|
|
1246
|
+
const carried = result.carry_forward;
|
|
1247
|
+
console.log(` Log`);
|
|
1248
|
+
console.log(` ─────────────────────────────────────────────────`);
|
|
1249
|
+
console.log(` Status: ${carried.status}`);
|
|
1250
|
+
console.log(` Issued at: ${carried.issued_at}`);
|
|
1251
|
+
console.log(` Issued epoch: ${carried.issued_epoch}`);
|
|
1252
|
+
console.log(` Logged at: ${carried.logged_at}`);
|
|
1253
|
+
console.log(` Logged epoch: ${carried.logged_epoch}`);
|
|
1254
|
+
console.log(` Leaf index: ${carried.leaf_index}`);
|
|
1255
|
+
console.log('');
|
|
1256
|
+
}
|
|
647
1257
|
console.log(` Overall: ${result.overall.toUpperCase()}`);
|
|
648
1258
|
if (result.errors.length > 0) {
|
|
649
1259
|
console.log(` Errors: ${result.errors.join(', ')}`);
|
|
@@ -652,6 +1262,7 @@ async function main() {
|
|
|
652
1262
|
}
|
|
653
1263
|
switch (result.overall) {
|
|
654
1264
|
case 'verified':
|
|
1265
|
+
case 'verified_carried_forward':
|
|
655
1266
|
return 0;
|
|
656
1267
|
case 'failed':
|
|
657
1268
|
return 1;
|
|
@@ -661,10 +1272,43 @@ async function main() {
|
|
|
661
1272
|
return 1;
|
|
662
1273
|
}
|
|
663
1274
|
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Node's fetch keeps its undici agent on this symbol after the first request.
|
|
1277
|
+
* There is no public handle for that pool. Closing it lets the loop drain.
|
|
1278
|
+
*/
|
|
1279
|
+
const UNDICI_GLOBAL_DISPATCHER = Symbol.for('undici.globalDispatcher.1');
|
|
1280
|
+
function fetchDispatcher() {
|
|
1281
|
+
const bag = globalThis;
|
|
1282
|
+
return bag[UNDICI_GLOBAL_DISPATCHER];
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* process.exit() while a fetch keep-alive socket is already CLOSING aborts
|
|
1286
|
+
* on Windows: Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)
|
|
1287
|
+
* (src/win/async.c). Set the code and close the pool so the loop can drain.
|
|
1288
|
+
*/
|
|
1289
|
+
async function finish(code) {
|
|
1290
|
+
process.exitCode = code;
|
|
1291
|
+
const dispatcher = fetchDispatcher();
|
|
1292
|
+
if (!dispatcher)
|
|
1293
|
+
return;
|
|
1294
|
+
try {
|
|
1295
|
+
const closing = dispatcher.close?.();
|
|
1296
|
+
if (closing)
|
|
1297
|
+
await closing;
|
|
1298
|
+
}
|
|
1299
|
+
catch {
|
|
1300
|
+
try {
|
|
1301
|
+
await dispatcher.destroy?.();
|
|
1302
|
+
}
|
|
1303
|
+
catch {
|
|
1304
|
+
// The verification result is already the exit code.
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
664
1308
|
main()
|
|
665
|
-
.then((code) =>
|
|
1309
|
+
.then((code) => finish(code))
|
|
666
1310
|
.catch((err) => {
|
|
667
1311
|
console.error('Unexpected error:', err);
|
|
668
|
-
|
|
1312
|
+
return finish(3);
|
|
669
1313
|
});
|
|
670
1314
|
//# sourceMappingURL=cli.js.map
|