chit402-verify 0.3.4 → 0.4.0
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/LICENSE +201 -201
- package/README.md +270 -82
- package/dist/anchor-trust.d.ts +144 -0
- package/dist/anchor-trust.d.ts.map +1 -0
- package/dist/anchor-trust.js +441 -0
- package/dist/anchor-trust.js.map +1 -0
- package/dist/anchor-witness.d.ts +265 -0
- package/dist/anchor-witness.d.ts.map +1 -0
- package/dist/anchor-witness.js +833 -0
- package/dist/anchor-witness.js.map +1 -0
- package/dist/base-payer.d.ts +65 -0
- package/dist/base-payer.d.ts.map +1 -0
- package/dist/base-payer.js +222 -0
- package/dist/base-payer.js.map +1 -0
- package/dist/binding.d.ts +52 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +80 -0
- package/dist/binding.js.map +1 -0
- package/dist/canonical-preimage.d.ts +9 -0
- package/dist/canonical-preimage.d.ts.map +1 -0
- package/dist/canonical-preimage.js +25 -0
- package/dist/canonical-preimage.js.map +1 -0
- 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.d.ts +18 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2090 -0
- package/dist/cli.js.map +1 -0
- 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/head-binding.d.ts +24 -0
- package/dist/head-binding.d.ts.map +1 -0
- package/dist/head-binding.js +61 -0
- package/dist/head-binding.js.map +1 -0
- package/dist/index.d.ts +585 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1648 -0
- package/dist/index.js.map +1 -0
- package/dist/issuer-history.d.ts +151 -0
- package/dist/issuer-history.d.ts.map +1 -0
- package/dist/issuer-history.js +595 -0
- package/dist/issuer-history.js.map +1 -0
- 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/jcs.d.ts +8 -0
- package/dist/jcs.d.ts.map +1 -0
- package/dist/jcs.js +64 -0
- package/dist/jcs.js.map +1 -0
- package/dist/jws.d.ts +46 -0
- package/dist/jws.d.ts.map +1 -0
- package/dist/jws.js +129 -0
- package/dist/jws.js.map +1 -0
- package/dist/payer.d.ts +65 -0
- package/dist/payer.d.ts.map +1 -0
- package/dist/payer.js +148 -0
- package/dist/payer.js.map +1 -0
- package/dist/preimage.d.ts +78 -0
- package/dist/preimage.d.ts.map +1 -0
- package/dist/preimage.js +363 -0
- package/dist/preimage.js.map +1 -0
- package/dist/receipt-lane.d.ts +235 -0
- package/dist/receipt-lane.d.ts.map +1 -0
- package/dist/receipt-lane.js +446 -0
- package/dist/receipt-lane.js.map +1 -0
- package/dist/receipt-version.d.ts +98 -0
- package/dist/receipt-version.d.ts.map +1 -0
- package/dist/receipt-version.js +364 -0
- package/dist/receipt-version.js.map +1 -0
- package/dist/refusal.d.ts +93 -0
- package/dist/refusal.d.ts.map +1 -0
- package/dist/refusal.js +249 -0
- package/dist/refusal.js.map +1 -0
- 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/shell.d.ts +65 -0
- package/dist/shell.d.ts.map +1 -0
- package/dist/shell.js +268 -0
- package/dist/shell.js.map +1 -0
- package/dist/solana-payer.d.ts +99 -0
- package/dist/solana-payer.d.ts.map +1 -0
- package/dist/solana-payer.js +258 -0
- package/dist/solana-payer.js.map +1 -0
- package/dist/v11-receipt.d.ts +23 -0
- package/dist/v11-receipt.d.ts.map +1 -0
- package/dist/v11-receipt.js +368 -0
- package/dist/v11-receipt.js.map +1 -0
- package/dist/verdict.d.ts +94 -0
- package/dist/verdict.d.ts.map +1 -0
- package/dist/verdict.js +381 -0
- package/dist/verdict.js.map +1 -0
- package/issuer-history.snapshot.json +1 -0
- package/package.json +70 -59
- package/cli.js +0 -27
- package/index.cjs +0 -8
- package/index.d.ts +0 -5
- package/index.js +0 -7
package/dist/cli.js
ADDED
|
@@ -0,0 +1,2090 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* xfuel-verify CLI — Offline receipt verification.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* xfuel-verify receipt.json
|
|
8
|
+
* xfuel-verify receipt.json --check-nullifier
|
|
9
|
+
* xfuel-verify receipt.json --rpc https://mainnet.base.org
|
|
10
|
+
* xfuel-verify receipt.json inclusion.json head.json --rpc
|
|
11
|
+
* cat receipt.json | xfuel-verify -
|
|
12
|
+
*
|
|
13
|
+
* Exit codes:
|
|
14
|
+
* 0 = VERIFIED (every check passed)
|
|
15
|
+
* 1 = INVALID
|
|
16
|
+
* 2 = COULD_NOT_CHECK
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
const node_module_1 = require("node:module");
|
|
20
|
+
const node_fs_1 = require("node:fs");
|
|
21
|
+
const index_js_1 = require("./index.js");
|
|
22
|
+
const anchor_witness_js_1 = require("./anchor-witness.js");
|
|
23
|
+
const jws_js_1 = require("./jws.js");
|
|
24
|
+
const shell_js_1 = require("./shell.js");
|
|
25
|
+
const preimage_js_1 = require("./preimage.js");
|
|
26
|
+
const issuer_history_js_1 = require("./issuer-history.js");
|
|
27
|
+
const base_payer_js_1 = require("./base-payer.js");
|
|
28
|
+
const solana_payer_js_1 = require("./solana-payer.js");
|
|
29
|
+
const verdict_js_1 = require("./verdict.js");
|
|
30
|
+
const canonical_preimage_js_1 = require("./canonical-preimage.js");
|
|
31
|
+
const v11_receipt_js_1 = require("./v11-receipt.js");
|
|
32
|
+
const receipt_version_js_1 = require("./receipt-version.js");
|
|
33
|
+
const issuer_pin_js_1 = require("./issuer-pin.js");
|
|
34
|
+
const anchor_trust_js_1 = require("./anchor-trust.js");
|
|
35
|
+
const HELP = `
|
|
36
|
+
chit402-verify — verify a Chit402 receipt
|
|
37
|
+
|
|
38
|
+
Usage:
|
|
39
|
+
chit402-verify <id|url|receipt.json>
|
|
40
|
+
chit402-verify - < receipt.json
|
|
41
|
+
chit402-verify <receipt.json> --issuer-history <history.json>
|
|
42
|
+
chit402-verify <receipt.json> --json
|
|
43
|
+
|
|
44
|
+
Exit codes:
|
|
45
|
+
0 VERIFIED every check passed
|
|
46
|
+
1 INVALID tamper, bad signature, revoked key, NOT_IN_LOG
|
|
47
|
+
2 COULD_NOT_CHECK log or anchor was not checked
|
|
48
|
+
|
|
49
|
+
Flags:
|
|
50
|
+
--issuer-history <file> History that prefix-extends the bundled snapshot
|
|
51
|
+
--json Print the verdict JSON
|
|
52
|
+
--help
|
|
53
|
+
--version
|
|
54
|
+
|
|
55
|
+
${(0, receipt_version_js_1.formatSupportedReceiptVersions)()}
|
|
56
|
+
`;
|
|
57
|
+
function parseArgs(args) {
|
|
58
|
+
const result = {
|
|
59
|
+
file: null,
|
|
60
|
+
jwksFile: null,
|
|
61
|
+
jwksUrl: null,
|
|
62
|
+
fetchJwks: false,
|
|
63
|
+
trustedKids: null,
|
|
64
|
+
noTrustedKid: false,
|
|
65
|
+
checkNullifier: false,
|
|
66
|
+
checkPayer: false,
|
|
67
|
+
rpcUrl: null,
|
|
68
|
+
solanaRpcUrl: null,
|
|
69
|
+
sawRpc: false,
|
|
70
|
+
anchorFlag: false,
|
|
71
|
+
inclusionFile: null,
|
|
72
|
+
headFile: null,
|
|
73
|
+
epochRecordFile: null,
|
|
74
|
+
epochUrl: null,
|
|
75
|
+
carryOldHeadFile: null,
|
|
76
|
+
carryOldInclusionFile: null,
|
|
77
|
+
carryHeadFile: null,
|
|
78
|
+
carryInclusionFiles: [],
|
|
79
|
+
positionals: [],
|
|
80
|
+
json: false,
|
|
81
|
+
quiet: false,
|
|
82
|
+
help: false,
|
|
83
|
+
strictIssuerHistory: false,
|
|
84
|
+
issuerHistoryFile: null,
|
|
85
|
+
canonicalPreimageFile: null,
|
|
86
|
+
noIssuerHistory: false,
|
|
87
|
+
strict: false,
|
|
88
|
+
noPreimage: false,
|
|
89
|
+
issuerPinFile: null,
|
|
90
|
+
issuerPinCommit: null,
|
|
91
|
+
issuerPinSha256: null,
|
|
92
|
+
issuerPinSigFile: null,
|
|
93
|
+
issuerWitnessesFile: null,
|
|
94
|
+
issuerPriorPinFile: null,
|
|
95
|
+
issuerPriorCommit: null,
|
|
96
|
+
issuerPriorSha256: null,
|
|
97
|
+
issuerControlFile: null,
|
|
98
|
+
version: false,
|
|
99
|
+
anchorWalletsFile: null,
|
|
100
|
+
acceptShell: false,
|
|
101
|
+
jwsFile: null,
|
|
102
|
+
salt: null,
|
|
103
|
+
open: [],
|
|
104
|
+
url: null,
|
|
105
|
+
};
|
|
106
|
+
for (let i = 0; i < args.length; i++) {
|
|
107
|
+
const arg = args[i];
|
|
108
|
+
if (arg === '--version' || arg === '-V') {
|
|
109
|
+
result.version = true;
|
|
110
|
+
}
|
|
111
|
+
else if (arg === '--help' || arg === '-h') {
|
|
112
|
+
result.help = true;
|
|
113
|
+
}
|
|
114
|
+
else if (arg === '--check-nullifier') {
|
|
115
|
+
result.checkNullifier = true;
|
|
116
|
+
}
|
|
117
|
+
else if (arg === '--check-payer') {
|
|
118
|
+
result.checkPayer = true;
|
|
119
|
+
}
|
|
120
|
+
else if (arg === '--jwks-file' && args[i + 1]) {
|
|
121
|
+
result.jwksFile = args[++i];
|
|
122
|
+
}
|
|
123
|
+
else if (arg === '--jwks-url' && args[i + 1]) {
|
|
124
|
+
result.jwksUrl = args[++i];
|
|
125
|
+
}
|
|
126
|
+
else if (arg === '--fetch-jwks') {
|
|
127
|
+
result.fetchJwks = true;
|
|
128
|
+
}
|
|
129
|
+
else if (arg === '--trusted-kid' && args[i + 1]) {
|
|
130
|
+
result.trustedKids = result.trustedKids || [];
|
|
131
|
+
result.trustedKids.push(args[++i]);
|
|
132
|
+
}
|
|
133
|
+
else if (arg === '--no-trusted-kid') {
|
|
134
|
+
result.noTrustedKid = true;
|
|
135
|
+
}
|
|
136
|
+
else if (arg === '--rpc') {
|
|
137
|
+
result.sawRpc = true;
|
|
138
|
+
const next = args[i + 1];
|
|
139
|
+
if (next && /^https?:\/\//i.test(next))
|
|
140
|
+
result.rpcUrl = args[++i];
|
|
141
|
+
else
|
|
142
|
+
result.anchorFlag = true;
|
|
143
|
+
}
|
|
144
|
+
else if (arg === '--inclusion' && args[i + 1]) {
|
|
145
|
+
result.inclusionFile = args[++i];
|
|
146
|
+
}
|
|
147
|
+
else if (arg === '--head' && args[i + 1]) {
|
|
148
|
+
result.headFile = args[++i];
|
|
149
|
+
}
|
|
150
|
+
else if (arg === '--epoch-record' && args[i + 1]) {
|
|
151
|
+
result.epochRecordFile = args[++i];
|
|
152
|
+
}
|
|
153
|
+
else if (arg === '--epoch-url' && args[i + 1]) {
|
|
154
|
+
result.epochUrl = args[++i];
|
|
155
|
+
}
|
|
156
|
+
else if (arg === '--carry-old-head' && args[i + 1]) {
|
|
157
|
+
result.carryOldHeadFile = args[++i];
|
|
158
|
+
}
|
|
159
|
+
else if (arg === '--carry-old-inclusion' && args[i + 1]) {
|
|
160
|
+
result.carryOldInclusionFile = args[++i];
|
|
161
|
+
}
|
|
162
|
+
else if (arg === '--carry-head' && args[i + 1]) {
|
|
163
|
+
result.carryHeadFile = args[++i];
|
|
164
|
+
}
|
|
165
|
+
else if (arg === '--carry-inclusion' && args[i + 1]) {
|
|
166
|
+
result.carryInclusionFiles.push(args[++i]);
|
|
167
|
+
}
|
|
168
|
+
else if (arg === '--solana-rpc' && args[i + 1]) {
|
|
169
|
+
result.solanaRpcUrl = args[++i];
|
|
170
|
+
}
|
|
171
|
+
else if (arg === '--issuer-history' && args[i + 1]) {
|
|
172
|
+
result.issuerHistoryFile = args[++i];
|
|
173
|
+
}
|
|
174
|
+
else if (arg === '--salt' && args[i + 1]) {
|
|
175
|
+
result.salt = args[++i];
|
|
176
|
+
}
|
|
177
|
+
else if (arg === '--open' && args[i + 1]) {
|
|
178
|
+
result.open.push(args[++i]);
|
|
179
|
+
}
|
|
180
|
+
else if (arg === '--canonical-preimage' && args[i + 1]) {
|
|
181
|
+
result.canonicalPreimageFile = args[++i];
|
|
182
|
+
}
|
|
183
|
+
else if (arg === '--anchor-wallets-file' && args[i + 1]) {
|
|
184
|
+
result.anchorWalletsFile = args[++i];
|
|
185
|
+
}
|
|
186
|
+
else if (arg === '--no-preimage') {
|
|
187
|
+
result.noPreimage = true;
|
|
188
|
+
}
|
|
189
|
+
else if (arg === '--jws' && args[i + 1]) {
|
|
190
|
+
result.jwsFile = args[++i];
|
|
191
|
+
}
|
|
192
|
+
else if (arg === '--url' && args[i + 1]) {
|
|
193
|
+
result.url = args[++i];
|
|
194
|
+
}
|
|
195
|
+
else if (arg === '--issuer-pin' && args[i + 1]) {
|
|
196
|
+
result.issuerPinFile = args[++i];
|
|
197
|
+
}
|
|
198
|
+
else if (arg === '--issuer-pin-commit' && args[i + 1]) {
|
|
199
|
+
result.issuerPinCommit = args[++i];
|
|
200
|
+
}
|
|
201
|
+
else if (arg === '--issuer-pin-sha256' && args[i + 1]) {
|
|
202
|
+
result.issuerPinSha256 = args[++i];
|
|
203
|
+
}
|
|
204
|
+
else if (arg === '--issuer-pin-sig' && args[i + 1]) {
|
|
205
|
+
result.issuerPinSigFile = args[++i];
|
|
206
|
+
}
|
|
207
|
+
else if (arg === '--issuer-witnesses' && args[i + 1]) {
|
|
208
|
+
result.issuerWitnessesFile = args[++i];
|
|
209
|
+
}
|
|
210
|
+
else if (arg === '--issuer-prior-pin' && args[i + 1]) {
|
|
211
|
+
result.issuerPriorPinFile = args[++i];
|
|
212
|
+
}
|
|
213
|
+
else if (arg === '--issuer-prior-commit' && args[i + 1]) {
|
|
214
|
+
result.issuerPriorCommit = args[++i];
|
|
215
|
+
}
|
|
216
|
+
else if (arg === '--issuer-prior-sha256' && args[i + 1]) {
|
|
217
|
+
result.issuerPriorSha256 = args[++i];
|
|
218
|
+
}
|
|
219
|
+
else if (arg === '--issuer-control' && args[i + 1]) {
|
|
220
|
+
result.issuerControlFile = args[++i];
|
|
221
|
+
}
|
|
222
|
+
else if (arg === '--json') {
|
|
223
|
+
result.json = true;
|
|
224
|
+
}
|
|
225
|
+
else if (arg === '--quiet' || arg === '-q') {
|
|
226
|
+
result.quiet = true;
|
|
227
|
+
}
|
|
228
|
+
else if (!arg.startsWith('-')) {
|
|
229
|
+
result.positionals.push(arg);
|
|
230
|
+
if (!result.file)
|
|
231
|
+
result.file = arg;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
function readReceipt(file) {
|
|
237
|
+
let content;
|
|
238
|
+
if (file === '-') {
|
|
239
|
+
content = (0, node_fs_1.readFileSync)(0, 'utf8');
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
content = (0, node_fs_1.readFileSync)(file, 'utf8');
|
|
243
|
+
}
|
|
244
|
+
return JSON.parse(content);
|
|
245
|
+
}
|
|
246
|
+
function formatAmount(units) {
|
|
247
|
+
if (!units)
|
|
248
|
+
return '—';
|
|
249
|
+
const n = Number(units);
|
|
250
|
+
if (!Number.isFinite(n))
|
|
251
|
+
return units;
|
|
252
|
+
const usd = n / 1e6;
|
|
253
|
+
return `$${usd.toFixed(usd >= 0.01 ? 2 : 6)} (${units} units)`;
|
|
254
|
+
}
|
|
255
|
+
function readJson(file) {
|
|
256
|
+
const content = file === '-' ? (0, node_fs_1.readFileSync)(0, 'utf8') : (0, node_fs_1.readFileSync)(file, 'utf8');
|
|
257
|
+
return JSON.parse(content);
|
|
258
|
+
}
|
|
259
|
+
function headStatus(file) {
|
|
260
|
+
try {
|
|
261
|
+
const doc = readJson(file);
|
|
262
|
+
return typeof doc?.status === 'string' ? doc.status : null;
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function printLane(lane) {
|
|
269
|
+
const bit = (value) => (value == null ? 'unknown' : (value ? 'true' : 'false'));
|
|
270
|
+
console.log(` Receipt lane (unsigned)`);
|
|
271
|
+
console.log(` ─────────────────────────────────────────────────`);
|
|
272
|
+
console.log(` book_seq: ${lane.book_seq ?? '—'}`);
|
|
273
|
+
console.log(` settled_by: ${lane.settled_by ?? 'null'}`);
|
|
274
|
+
console.log(` settled: ${bit(lane.settled)}`);
|
|
275
|
+
console.log(` anchor_changed_since_binding: ${bit(lane.anchor_changed_since_binding)}`);
|
|
276
|
+
console.log(` freeze: ${lane.freeze ? 'YES' : 'no'}`);
|
|
277
|
+
if (lane.reason)
|
|
278
|
+
console.log(` reason: ${lane.reason}`);
|
|
279
|
+
console.log(` classification:${lane.classification}`);
|
|
280
|
+
console.log(` ordering: ${lane.ordering}`);
|
|
281
|
+
console.log(` boundary: ${lane.boundary}`);
|
|
282
|
+
if (lane.local_check) {
|
|
283
|
+
console.log(` local_check: payee ${lane.local_check.payee} amount ${lane.local_check.amount_atomic} (not a payment)`);
|
|
284
|
+
}
|
|
285
|
+
console.log(` ${lane.rule}`);
|
|
286
|
+
console.log('');
|
|
287
|
+
}
|
|
288
|
+
function printAnchor(result, json, quiet, overallLabel) {
|
|
289
|
+
if (json) {
|
|
290
|
+
console.log(JSON.stringify(result, null, 2));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (quiet && result.overall !== 'failed' && result.overall !== 'not_in_log')
|
|
294
|
+
return;
|
|
295
|
+
const mark = (ok) => (ok ? '✓ YES' : '✗ NO');
|
|
296
|
+
console.log('');
|
|
297
|
+
console.log(' Anchored receipt root');
|
|
298
|
+
console.log(' ─────────────────────────────────────────────────');
|
|
299
|
+
console.log(` Root: ${result.root || '—'}`);
|
|
300
|
+
console.log(` Head signature:${result.head_signature.valid ? ' ✓ YES' : ' ✗ NO'}${result.head_signature.trust ? ` (${result.head_signature.trust})` : ''}`);
|
|
301
|
+
if (result.head_signature.kid)
|
|
302
|
+
console.log(` Head kid: ${result.head_signature.kid}`);
|
|
303
|
+
if (result.head_signature.message)
|
|
304
|
+
console.log(` Head reason: ${result.head_signature.message}`);
|
|
305
|
+
const unlogged = result.inclusion.unlogged_reason ? ` unlogged:${result.inclusion.unlogged_reason}` : '';
|
|
306
|
+
if (result.overall === 'pending') {
|
|
307
|
+
console.log(` Inclusion: PENDING${result.inclusion.reason ? ` (${result.inclusion.reason})` : ''}`);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
console.log(` Inclusion: ${mark(result.inclusion.valid)}${result.inclusion.reason ? ` (${result.inclusion.reason})` : ''}${unlogged}`);
|
|
311
|
+
}
|
|
312
|
+
console.log(` Leaf source: ${result.inclusion.leaf_source}`);
|
|
313
|
+
console.log(` Solana: ${result.solana.checked ? mark(result.solana.valid) : (result.solana.reason || 'not checked')}`);
|
|
314
|
+
if (result.solana.signature)
|
|
315
|
+
console.log(` Signature: ${result.solana.signature}`);
|
|
316
|
+
if (result.solana.slot != null)
|
|
317
|
+
console.log(` Slot: ${result.solana.slot}`);
|
|
318
|
+
if (result.solana.cluster)
|
|
319
|
+
console.log(` Cluster: ${result.solana.cluster}`);
|
|
320
|
+
if (result.solana.fee_payer)
|
|
321
|
+
console.log(` Fee payer: ${result.solana.fee_payer}`);
|
|
322
|
+
if (result.solana.memo)
|
|
323
|
+
console.log(` Memo: ${result.solana.memo}`);
|
|
324
|
+
if (result.solana.reason && !result.solana.valid)
|
|
325
|
+
console.log(` Solana reason: ${result.solana.reason}`);
|
|
326
|
+
if (result.issuer_pin?.checked) {
|
|
327
|
+
console.log(` Issuer pin: ${result.issuer_pin.ok ? '✓ YES' : '✗ NO'}${result.issuer_pin.code ? ` (${result.issuer_pin.code})` : ''}`);
|
|
328
|
+
}
|
|
329
|
+
console.log(` Base: ${result.base.checked ? mark(result.base.valid) : (result.base.reason || 'not checked')}`);
|
|
330
|
+
if (result.base.from)
|
|
331
|
+
console.log(` Base from: ${result.base.from}`);
|
|
332
|
+
if (result.base.tx)
|
|
333
|
+
console.log(` Base tx: ${result.base.tx}`);
|
|
334
|
+
if (result.base.chain_id != null)
|
|
335
|
+
console.log(` Chain id: ${result.base.chain_id}`);
|
|
336
|
+
if (result.base.reason && !result.base.valid)
|
|
337
|
+
console.log(` Base reason: ${result.base.reason}`);
|
|
338
|
+
console.log('');
|
|
339
|
+
if (result.overall !== 'failed' && result.overall !== 'not_in_log') {
|
|
340
|
+
console.log(' What this proves');
|
|
341
|
+
console.log(' ─────────────────────────────────────────────────');
|
|
342
|
+
for (const line of result.proves)
|
|
343
|
+
console.log(` ${line}`);
|
|
344
|
+
console.log('');
|
|
345
|
+
}
|
|
346
|
+
console.log(' What this does not prove');
|
|
347
|
+
console.log(' ─────────────────────────────────────────────────');
|
|
348
|
+
for (const line of result.does_not_prove)
|
|
349
|
+
console.log(` ${line}`);
|
|
350
|
+
console.log('');
|
|
351
|
+
if (result.overall === 'pending') {
|
|
352
|
+
console.log(` Anchored size: ${result.inclusion.anchored_tree_size ?? '—'}`);
|
|
353
|
+
console.log(` Live size: ${result.inclusion.live_tree_size ?? '—'}`);
|
|
354
|
+
}
|
|
355
|
+
console.log(` Overall: ${overallLabel || result.overall.toUpperCase()}`);
|
|
356
|
+
if (result.errors.length > 0)
|
|
357
|
+
console.log(` Errors: ${result.errors.join(', ')}`);
|
|
358
|
+
if (result.errors.includes(anchor_trust_js_1.LEGACY_HEAD_UNPINNED_SIGNER)) {
|
|
359
|
+
console.log(` ${anchor_trust_js_1.HEAD_TRUST_MESSAGES[anchor_trust_js_1.LEGACY_HEAD_UNPINNED_SIGNER]}`);
|
|
360
|
+
}
|
|
361
|
+
console.log('');
|
|
362
|
+
}
|
|
363
|
+
function headNeedsEpochRecord(head) {
|
|
364
|
+
const schema = head.schema;
|
|
365
|
+
const version = head.payload_version;
|
|
366
|
+
return head.epoch != null || schema === 'chit402.tree_head.v2' || version === 2;
|
|
367
|
+
}
|
|
368
|
+
function epochUrlFromReceipt(receipt) {
|
|
369
|
+
const raw = receipt?.verify_url;
|
|
370
|
+
if (!raw)
|
|
371
|
+
return null;
|
|
372
|
+
try {
|
|
373
|
+
return `${new URL(raw).origin}/v1/receipts/tree/epoch`;
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async function loadEpochRecord(args, receipt) {
|
|
380
|
+
if (args.epochRecordFile) {
|
|
381
|
+
return JSON.parse((0, node_fs_1.readFileSync)(args.epochRecordFile, 'utf8'));
|
|
382
|
+
}
|
|
383
|
+
const url = args.epochUrl || epochUrlFromReceipt(receipt);
|
|
384
|
+
if (!url)
|
|
385
|
+
return null;
|
|
386
|
+
const res = await fetch(url);
|
|
387
|
+
if (!res.ok)
|
|
388
|
+
return null;
|
|
389
|
+
const body = await res.json();
|
|
390
|
+
if (!body?.issuer_signature?.jws)
|
|
391
|
+
return null;
|
|
392
|
+
return body;
|
|
393
|
+
}
|
|
394
|
+
function epochSignatureOk(record, jws, jwks, trustedKids) {
|
|
395
|
+
const sig = record.issuer_signature;
|
|
396
|
+
const embedded = sig && 'issuer_jwk' in sig
|
|
397
|
+
? sig.issuer_jwk
|
|
398
|
+
: undefined;
|
|
399
|
+
const kid = (0, jws_js_1.readJwsHeader)(jws)?.kid;
|
|
400
|
+
const keys = [];
|
|
401
|
+
for (const key of jwks?.keys || []) {
|
|
402
|
+
if (!key || key.kty !== 'EC')
|
|
403
|
+
continue;
|
|
404
|
+
const jwk = key;
|
|
405
|
+
if (kid && jwk.kid !== kid)
|
|
406
|
+
continue;
|
|
407
|
+
keys.push(jwk);
|
|
408
|
+
}
|
|
409
|
+
if (embedded && trustedKids?.includes((0, jws_js_1.jwkThumbprint)(embedded)))
|
|
410
|
+
keys.push(embedded);
|
|
411
|
+
for (const key of keys) {
|
|
412
|
+
const verified = (0, jws_js_1.verifyIssuerJws)(jws, key);
|
|
413
|
+
if (!verified.valid || !verified.payload)
|
|
414
|
+
continue;
|
|
415
|
+
const payload = verified.payload;
|
|
416
|
+
if (JSON.stringify(payload.epochs ?? null) !== JSON.stringify(record.epochs ?? null))
|
|
417
|
+
return false;
|
|
418
|
+
if (JSON.stringify(payload.orphans ?? []) !== JSON.stringify(record.orphans ?? []))
|
|
419
|
+
return false;
|
|
420
|
+
const payloadUnlogged = payload.unlogged === undefined ? null : payload.unlogged;
|
|
421
|
+
const recordUnlogged = record.unlogged === undefined ? null : record.unlogged;
|
|
422
|
+
if (JSON.stringify(payloadUnlogged) !== JSON.stringify(recordUnlogged))
|
|
423
|
+
return false;
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
function packageVersion() {
|
|
429
|
+
const require = (0, node_module_1.createRequire)(__filename);
|
|
430
|
+
const pkg = require('../package.json');
|
|
431
|
+
return pkg.version || '0.0.0';
|
|
432
|
+
}
|
|
433
|
+
const REMOVED_040 = {
|
|
434
|
+
'--strict': 'removed in 0.4: a skipped key window is no longer a separate result',
|
|
435
|
+
'--no-issuer-history': 'removed in 0.4: the history check is always on',
|
|
436
|
+
'--accept-no-key-window': 'removed in 0.4: VERIFIED_NO_KEY_WINDOW is retired',
|
|
437
|
+
'--accept-shell': 'removed in 0.4: a shell is not a verified payment',
|
|
438
|
+
};
|
|
439
|
+
function removedFlag(argv) {
|
|
440
|
+
for (const arg of argv) {
|
|
441
|
+
if (REMOVED_040[arg])
|
|
442
|
+
return REMOVED_040[arg];
|
|
443
|
+
}
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
async function resolvedHistory(args, receipt, jwks, trustedKids) {
|
|
447
|
+
const fileBytes = args.issuerHistoryFile ? (0, node_fs_1.readFileSync)(args.issuerHistoryFile, 'utf8') : null;
|
|
448
|
+
return (0, issuer_history_js_1.resolveIssuerHistory)({
|
|
449
|
+
fileBytes,
|
|
450
|
+
receipt,
|
|
451
|
+
jwks,
|
|
452
|
+
trustedKids,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
function historyOptions(resolved) {
|
|
456
|
+
return {
|
|
457
|
+
issuerHistory: resolved.document,
|
|
458
|
+
issuerHistoryBytes: resolved.bytes,
|
|
459
|
+
fetchIssuerHistory: false,
|
|
460
|
+
skipIssuerHistory: false,
|
|
461
|
+
strictIssuerHistory: false,
|
|
462
|
+
labelSkippedKeyWindow: false,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
function printVerdict(json, verdict, legacy, legacyOverall) {
|
|
466
|
+
const line = (0, verdict_js_1.overallLine)(verdict, legacyOverall);
|
|
467
|
+
if (json) {
|
|
468
|
+
const overall = line === 'NOT_IN_LOG'
|
|
469
|
+
? 'not_in_log'
|
|
470
|
+
: line === 'VERIFIED_CARRIED_FORWARD'
|
|
471
|
+
? 'verified_carried_forward'
|
|
472
|
+
: line === 'PENDING'
|
|
473
|
+
? 'pending'
|
|
474
|
+
: line === 'PARTIAL'
|
|
475
|
+
? 'partial'
|
|
476
|
+
: line === 'VERIFIED'
|
|
477
|
+
? 'verified'
|
|
478
|
+
: line === 'COULD_NOT_CHECK'
|
|
479
|
+
? 'could_not_check'
|
|
480
|
+
: 'failed';
|
|
481
|
+
console.log(JSON.stringify({ ...(legacy || {}), ...verdict, overall }, null, 2));
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
console.log(` Overall: ${line}`);
|
|
485
|
+
console.log(` ${verdict.message}`);
|
|
486
|
+
if (verdict.history.source === 'bundled' && verdict.history.as_of) {
|
|
487
|
+
console.log(` Issuer history: as of ${verdict.history.as_of}`);
|
|
488
|
+
}
|
|
489
|
+
if (verdict.next_step)
|
|
490
|
+
console.log(` Next: ${verdict.next_step}`);
|
|
491
|
+
}
|
|
492
|
+
return verdict.exit_code;
|
|
493
|
+
}
|
|
494
|
+
function looksLikeTreeHead(value) {
|
|
495
|
+
if (!value || typeof value !== 'object')
|
|
496
|
+
return false;
|
|
497
|
+
const doc = value;
|
|
498
|
+
if (doc.status === 'not_yet_published')
|
|
499
|
+
return true;
|
|
500
|
+
if (typeof doc.schema === 'string' && doc.schema.startsWith('chit402.tree_head.'))
|
|
501
|
+
return true;
|
|
502
|
+
return Boolean(doc.anchors && doc.root && !doc.task_id);
|
|
503
|
+
}
|
|
504
|
+
function readOfflineWitness(args) {
|
|
505
|
+
let head = null;
|
|
506
|
+
let inclusion = null;
|
|
507
|
+
for (const file of args.positionals.slice(1)) {
|
|
508
|
+
let doc;
|
|
509
|
+
try {
|
|
510
|
+
doc = readJson(file);
|
|
511
|
+
}
|
|
512
|
+
catch (err) {
|
|
513
|
+
throw new Error(`Error reading ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
514
|
+
}
|
|
515
|
+
const row = doc;
|
|
516
|
+
if (looksLikeTreeHead(doc))
|
|
517
|
+
head = doc;
|
|
518
|
+
else if (doc && typeof doc === 'object' && (Array.isArray(row.proof) || row.leaf_index != null)) {
|
|
519
|
+
inclusion = doc;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return { head, inclusion };
|
|
523
|
+
}
|
|
524
|
+
function fileLooksLikeHead(file) {
|
|
525
|
+
if (!file)
|
|
526
|
+
return false;
|
|
527
|
+
try {
|
|
528
|
+
return looksLikeTreeHead(readJson(file));
|
|
529
|
+
}
|
|
530
|
+
catch {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
const LOG_FETCH_MS = 8000;
|
|
535
|
+
function inclusionHeadUrl(origin, inclusion) {
|
|
536
|
+
if (typeof inclusion.head_url === 'string' && inclusion.head_url.length > 0) {
|
|
537
|
+
try {
|
|
538
|
+
return new URL(inclusion.head_url, origin).toString();
|
|
539
|
+
}
|
|
540
|
+
catch {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const size = inclusion.anchored_tree_size ?? inclusion.tree_size;
|
|
545
|
+
if (size == null)
|
|
546
|
+
return null;
|
|
547
|
+
return `${origin}/v1/receipts/tree/head?tree_size=${encodeURIComponent(String(size))}`;
|
|
548
|
+
}
|
|
549
|
+
function missedLog(reason = 'log_unreachable') {
|
|
550
|
+
return {
|
|
551
|
+
logInclusion: 'could_not_check',
|
|
552
|
+
anchor: 'could_not_check',
|
|
553
|
+
logReason: reason,
|
|
554
|
+
log: null,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* A 404 `{ error: "not_in_tree" }` is unsigned. NOT_IN_LOG also needs the
|
|
559
|
+
* log's signed head, and that signature has to verify. A head with no
|
|
560
|
+
* signature leaves the check unfinished.
|
|
561
|
+
*/
|
|
562
|
+
async function absenceAgainstSignedHead(origin, history) {
|
|
563
|
+
try {
|
|
564
|
+
const res = await fetch(`${origin}/v1/receipts/tree/head`, {
|
|
565
|
+
headers: { accept: 'application/json' },
|
|
566
|
+
signal: AbortSignal.timeout(LOG_FETCH_MS),
|
|
567
|
+
});
|
|
568
|
+
if (!res.ok)
|
|
569
|
+
return { kind: 'unchecked' };
|
|
570
|
+
const head = await res.json();
|
|
571
|
+
const trust = (0, anchor_trust_js_1.verifyTreeHeadTrust)(head, {
|
|
572
|
+
trustedKids: index_js_1.DEFAULT_TRUSTED_ISSUER_KIDS,
|
|
573
|
+
issuerHistory: history,
|
|
574
|
+
});
|
|
575
|
+
if (trust.ok)
|
|
576
|
+
return { kind: 'not_in_log' };
|
|
577
|
+
if (!trust.reason || trust.reason === 'head_signature_missing')
|
|
578
|
+
return { kind: 'unchecked' };
|
|
579
|
+
return { kind: 'bad_head', reason: trust.reason };
|
|
580
|
+
}
|
|
581
|
+
catch {
|
|
582
|
+
return { kind: 'unchecked' };
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function hardLogReason(result) {
|
|
586
|
+
if (result.head_signature?.checked && result.head_signature.valid === false && result.head_signature.reason) {
|
|
587
|
+
return result.head_signature.reason;
|
|
588
|
+
}
|
|
589
|
+
const inclusionReason = result.inclusion?.reason;
|
|
590
|
+
if (result.inclusion
|
|
591
|
+
&& result.inclusion.valid === false
|
|
592
|
+
&& inclusionReason
|
|
593
|
+
&& inclusionReason !== 'pending_anchor'
|
|
594
|
+
&& inclusionReason !== 'not_in_tree') {
|
|
595
|
+
return inclusionReason;
|
|
596
|
+
}
|
|
597
|
+
const mismatch = (result.errors || []).find((err) => (/mismatch|head_signature|head_claims|head_key|inclusion_failed|bad_root|no_leaf/.test(err)));
|
|
598
|
+
return mismatch ? mismatch.replace(/^(solana|base):/, '') : null;
|
|
599
|
+
}
|
|
600
|
+
/** Shell absence check. A 200 proof is not verified here: there is no signed receipt. */
|
|
601
|
+
async function publicShellAbsence(receipt, history) {
|
|
602
|
+
try {
|
|
603
|
+
const origin = new URL(String(receipt.verify_url)).origin;
|
|
604
|
+
const task = receipt.task_id;
|
|
605
|
+
if (!task)
|
|
606
|
+
return false;
|
|
607
|
+
const res = await fetch(`${origin}/v1/receipts/${encodeURIComponent(String(task))}/inclusion`, {
|
|
608
|
+
headers: { accept: 'application/json' },
|
|
609
|
+
signal: AbortSignal.timeout(LOG_FETCH_MS),
|
|
610
|
+
});
|
|
611
|
+
if (res.status !== 404)
|
|
612
|
+
return false;
|
|
613
|
+
const body = await res.json().catch(() => ({}));
|
|
614
|
+
if (body.error !== 'not_in_tree' && body.reason !== 'not_in_tree')
|
|
615
|
+
return false;
|
|
616
|
+
const absence = await absenceAgainstSignedHead(origin, history);
|
|
617
|
+
return absence.kind === 'not_in_log';
|
|
618
|
+
}
|
|
619
|
+
catch {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
async function freshPublicLog(receipt, history = null) {
|
|
624
|
+
const missed = missedLog();
|
|
625
|
+
try {
|
|
626
|
+
const origin = new URL(String(receipt.verify_url)).origin;
|
|
627
|
+
const task = receipt.task_id;
|
|
628
|
+
if (!task)
|
|
629
|
+
return missed;
|
|
630
|
+
const res = await fetch(`${origin}/v1/receipts/${encodeURIComponent(String(task))}/inclusion`, {
|
|
631
|
+
headers: { accept: 'application/json' },
|
|
632
|
+
signal: AbortSignal.timeout(LOG_FETCH_MS),
|
|
633
|
+
});
|
|
634
|
+
if (res.status === 404) {
|
|
635
|
+
const body = await res.json().catch(() => ({}));
|
|
636
|
+
if (body.error !== 'not_in_tree' && body.reason !== 'not_in_tree')
|
|
637
|
+
return missed;
|
|
638
|
+
const absence = await absenceAgainstSignedHead(origin, history);
|
|
639
|
+
if (absence.kind === 'not_in_log') {
|
|
640
|
+
return { logInclusion: 'fail', anchor: 'pass', logReason: 'not_in_log', log: null };
|
|
641
|
+
}
|
|
642
|
+
if (absence.kind === 'bad_head') {
|
|
643
|
+
return { logInclusion: 'fail', anchor: 'fail', logReason: absence.reason, log: null };
|
|
644
|
+
}
|
|
645
|
+
return missed;
|
|
646
|
+
}
|
|
647
|
+
if (!res.ok)
|
|
648
|
+
return missed;
|
|
649
|
+
const inclusion = await res.json();
|
|
650
|
+
const headUrl = inclusionHeadUrl(origin, inclusion);
|
|
651
|
+
if (!headUrl)
|
|
652
|
+
return missed;
|
|
653
|
+
const headRes = await fetch(headUrl, {
|
|
654
|
+
headers: { accept: 'application/json' },
|
|
655
|
+
signal: AbortSignal.timeout(LOG_FETCH_MS),
|
|
656
|
+
});
|
|
657
|
+
if (!headRes.ok)
|
|
658
|
+
return missed;
|
|
659
|
+
const head = await headRes.json();
|
|
660
|
+
const result = await (0, anchor_witness_js_1.verifyAnchoredRoot)({
|
|
661
|
+
receipt: receipt,
|
|
662
|
+
inclusion,
|
|
663
|
+
head,
|
|
664
|
+
baseRpcUrl: process.env.BASE_RPC_URL || base_payer_js_1.BASE_RPC_URL,
|
|
665
|
+
solanaRpcUrl: process.env.SOLANA_RPC_URL || solana_payer_js_1.SOLANA_RPC_URL,
|
|
666
|
+
checkNewestAnchor: false,
|
|
667
|
+
issuerHistory: history,
|
|
668
|
+
trustedKids: index_js_1.DEFAULT_TRUSTED_ISSUER_KIDS,
|
|
669
|
+
});
|
|
670
|
+
if (result.overall === 'verified') {
|
|
671
|
+
return {
|
|
672
|
+
logInclusion: 'pass',
|
|
673
|
+
anchor: 'pass',
|
|
674
|
+
logReason: null,
|
|
675
|
+
log: {
|
|
676
|
+
leaf_index: inclusion.leaf_index ?? null,
|
|
677
|
+
tree_size: inclusion.tree_size ?? null,
|
|
678
|
+
root: result.root,
|
|
679
|
+
anchors: {
|
|
680
|
+
base: result.base.tx,
|
|
681
|
+
solana: result.solana.signature,
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
if (result.overall === 'not_in_log') {
|
|
687
|
+
return { logInclusion: 'fail', anchor: 'pass', logReason: 'not_in_log', log: null };
|
|
688
|
+
}
|
|
689
|
+
if (result.overall === 'pending')
|
|
690
|
+
return missedLog('pending_anchor');
|
|
691
|
+
const hard = hardLogReason(result);
|
|
692
|
+
if (result.overall === 'failed' && hard) {
|
|
693
|
+
const anchorFail = /head_|anchor|mismatch/.test(hard);
|
|
694
|
+
const inclusionFail = result.inclusion?.valid === false || /inclusion|leaf|root|no_leaf|bad_root/.test(hard);
|
|
695
|
+
return {
|
|
696
|
+
logInclusion: inclusionFail ? 'fail' : 'could_not_check',
|
|
697
|
+
anchor: anchorFail || !inclusionFail ? 'fail' : 'pass',
|
|
698
|
+
logReason: hard,
|
|
699
|
+
log: null,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
return missed;
|
|
703
|
+
}
|
|
704
|
+
catch {
|
|
705
|
+
return missed;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
function historyHostAllowed(url) {
|
|
709
|
+
try {
|
|
710
|
+
const parsed = new URL(url);
|
|
711
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
|
|
712
|
+
return false;
|
|
713
|
+
if (parsed.protocol === 'http:' && parsed.hostname !== '127.0.0.1' && parsed.hostname !== 'localhost')
|
|
714
|
+
return false;
|
|
715
|
+
if (parsed.protocol === 'https:') {
|
|
716
|
+
return index_js_1.DEFAULT_TRUSTED_JWKS_HOSTS.some((host) => host.toLowerCase() === parsed.hostname.toLowerCase());
|
|
717
|
+
}
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
catch {
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
async function loadAnchorIssuerHistory(args, receipt, jwks, trustedKids) {
|
|
725
|
+
return resolvedHistory(args, receipt, jwks, trustedKids);
|
|
726
|
+
}
|
|
727
|
+
async function loadAnchorWalletDocument(args, receipt) {
|
|
728
|
+
if (args.anchorWalletsFile) {
|
|
729
|
+
return JSON.parse((0, node_fs_1.readFileSync)(args.anchorWalletsFile, 'utf8'));
|
|
730
|
+
}
|
|
731
|
+
const historyUrl = (0, issuer_history_js_1.historyUrlFromReceipt)(receipt);
|
|
732
|
+
const epochOrigin = (() => {
|
|
733
|
+
if (!args.epochUrl)
|
|
734
|
+
return null;
|
|
735
|
+
try {
|
|
736
|
+
return new URL(args.epochUrl).origin;
|
|
737
|
+
}
|
|
738
|
+
catch {
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
})();
|
|
742
|
+
const origin = epochOrigin || (historyUrl ? new URL(historyUrl).origin : null);
|
|
743
|
+
if (!origin)
|
|
744
|
+
return null;
|
|
745
|
+
const url = `${origin}/.well-known/anchor-wallets.json`;
|
|
746
|
+
if (!historyHostAllowed(url))
|
|
747
|
+
return null;
|
|
748
|
+
try {
|
|
749
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(4000) });
|
|
750
|
+
if (res.status === 404)
|
|
751
|
+
return null;
|
|
752
|
+
if (!res.ok)
|
|
753
|
+
return null;
|
|
754
|
+
return await res.json();
|
|
755
|
+
}
|
|
756
|
+
catch {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function emitVersionFailure(args, errors) {
|
|
761
|
+
const reason = errors[0] || 'unsupported_version';
|
|
762
|
+
const unsupported = reason === 'unsupported_version';
|
|
763
|
+
const verdict = (0, verdict_js_1.buildVerifyVerdict)({
|
|
764
|
+
version: packageVersion(),
|
|
765
|
+
errors,
|
|
766
|
+
signatureChecked: !unsupported,
|
|
767
|
+
signatureValid: unsupported ? null : false,
|
|
768
|
+
historyMissing: unsupported,
|
|
769
|
+
logInclusion: 'unclaimed',
|
|
770
|
+
anchor: 'unclaimed',
|
|
771
|
+
});
|
|
772
|
+
if (!args.json)
|
|
773
|
+
console.log(reason);
|
|
774
|
+
return printVerdict(args.json, verdict, {
|
|
775
|
+
errors,
|
|
776
|
+
issuer_signature: { valid: false, checked: !unsupported, key_trusted: false, reason },
|
|
777
|
+
amount_usdc: null,
|
|
778
|
+
tx: null,
|
|
779
|
+
payer: { checked: false, valid: false },
|
|
780
|
+
}, unsupported ? 'could_not_check' : 'failed');
|
|
781
|
+
}
|
|
782
|
+
async function runAnchor(args) {
|
|
783
|
+
const receiptPath = args.file;
|
|
784
|
+
const inclusionPath = args.inclusionFile || args.positionals[1] || null;
|
|
785
|
+
const headPath = args.headFile || (args.inclusionFile ? null : args.positionals[2]) || null;
|
|
786
|
+
const headCandidates = [args.headFile, headPath, ...args.positionals];
|
|
787
|
+
if (headCandidates.some((file) => file && headStatus(file) === 'not_yet_published')) {
|
|
788
|
+
console.error('Tree head is not_yet_published.');
|
|
789
|
+
return 3;
|
|
790
|
+
}
|
|
791
|
+
if (!receiptPath || !inclusionPath || !headPath) {
|
|
792
|
+
console.error('Anchor check needs a receipt, an inclusion proof, and a tree head.');
|
|
793
|
+
console.error(' xfuel-verify receipt.json inclusion.json head.json --rpc');
|
|
794
|
+
return 3;
|
|
795
|
+
}
|
|
796
|
+
let receipt;
|
|
797
|
+
let inclusion;
|
|
798
|
+
let head;
|
|
799
|
+
try {
|
|
800
|
+
receipt = readJson(receiptPath);
|
|
801
|
+
inclusion = readJson(inclusionPath);
|
|
802
|
+
head = readJson(headPath);
|
|
803
|
+
}
|
|
804
|
+
catch (err) {
|
|
805
|
+
console.error(`Error reading anchor inputs: ${err instanceof Error ? err.message : String(err)}`);
|
|
806
|
+
return 3;
|
|
807
|
+
}
|
|
808
|
+
const anchorGate = (0, receipt_version_js_1.classifyReceiptDocument)(receipt);
|
|
809
|
+
if (!anchorGate.ok)
|
|
810
|
+
return emitVersionFailure(args, anchorGate.errors);
|
|
811
|
+
let fileJwks;
|
|
812
|
+
if (args.jwksFile) {
|
|
813
|
+
try {
|
|
814
|
+
fileJwks = JSON.parse((0, node_fs_1.readFileSync)(args.jwksFile, 'utf8'));
|
|
815
|
+
}
|
|
816
|
+
catch (err) {
|
|
817
|
+
console.error(`Error reading JWKS file: ${err instanceof Error ? err.message : String(err)}`);
|
|
818
|
+
return 3;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
const loaded = await (0, index_js_1.loadIssuerJwks)(receipt, {
|
|
822
|
+
jwks: fileJwks,
|
|
823
|
+
jwksUri: args.jwksUrl || undefined,
|
|
824
|
+
fetchJwks: args.fetchJwks,
|
|
825
|
+
});
|
|
826
|
+
const trustedKids = args.noTrustedKid
|
|
827
|
+
? []
|
|
828
|
+
: (args.trustedKids ?? [...index_js_1.DEFAULT_TRUSTED_ISSUER_KIDS]);
|
|
829
|
+
let resolved;
|
|
830
|
+
let anchorWalletDocument = null;
|
|
831
|
+
try {
|
|
832
|
+
resolved = await loadAnchorIssuerHistory(args, receipt, loaded.jwks, trustedKids);
|
|
833
|
+
anchorWalletDocument = await loadAnchorWalletDocument(args, receipt);
|
|
834
|
+
}
|
|
835
|
+
catch (err) {
|
|
836
|
+
console.error(`Error reading issuer trust files: ${err instanceof Error ? err.message : String(err)}`);
|
|
837
|
+
return 3;
|
|
838
|
+
}
|
|
839
|
+
let epochRecord = null;
|
|
840
|
+
let verifyEpochSignature;
|
|
841
|
+
if (headNeedsEpochRecord(head)) {
|
|
842
|
+
try {
|
|
843
|
+
epochRecord = await loadEpochRecord(args, receipt);
|
|
844
|
+
}
|
|
845
|
+
catch {
|
|
846
|
+
epochRecord = null;
|
|
847
|
+
}
|
|
848
|
+
if (epochRecord?.issuer_signature?.jws) {
|
|
849
|
+
verifyEpochSignature = (jws) => epochSignatureOk(epochRecord, jws, loaded.jwks, trustedKids);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
let issuerPin;
|
|
853
|
+
const pinClaim = (0, issuer_pin_js_1.readIssuerPinClaim)(receipt).claimed || (0, issuer_pin_js_1.readIssuerPinClaim)(head).claimed;
|
|
854
|
+
const pinRequested = Boolean(args.issuerPinFile) || pinClaim;
|
|
855
|
+
if (pinRequested) {
|
|
856
|
+
try {
|
|
857
|
+
const claimRef = (0, issuer_pin_js_1.readIssuerPinClaim)(receipt).ref || (0, issuer_pin_js_1.readIssuerPinClaim)(head).ref;
|
|
858
|
+
const ref = (args.issuerPinCommit || args.issuerPinSha256 || claimRef)
|
|
859
|
+
? {
|
|
860
|
+
commit: args.issuerPinCommit || claimRef?.commit || '',
|
|
861
|
+
path: claimRef?.path || 'docs/well-known/issuer-key.json',
|
|
862
|
+
sha256: (args.issuerPinSha256 || claimRef?.sha256 || '').toLowerCase(),
|
|
863
|
+
}
|
|
864
|
+
: null;
|
|
865
|
+
let witnesses;
|
|
866
|
+
if (args.issuerWitnessesFile)
|
|
867
|
+
witnesses = JSON.parse((0, node_fs_1.readFileSync)(args.issuerWitnessesFile, 'utf8'));
|
|
868
|
+
const priorRef = args.issuerPriorPinFile
|
|
869
|
+
? {
|
|
870
|
+
commit: args.issuerPriorCommit || '',
|
|
871
|
+
path: 'docs/well-known/issuer-key.json',
|
|
872
|
+
sha256: (args.issuerPriorSha256 || '').toLowerCase(),
|
|
873
|
+
}
|
|
874
|
+
: null;
|
|
875
|
+
issuerPin = {
|
|
876
|
+
pinBytes: args.issuerPinFile ? (0, node_fs_1.readFileSync)(args.issuerPinFile, 'utf8') : null,
|
|
877
|
+
sigBytes: args.issuerPinSigFile ? (0, node_fs_1.readFileSync)(args.issuerPinSigFile, 'utf8') : null,
|
|
878
|
+
ref,
|
|
879
|
+
witnesses,
|
|
880
|
+
priorPinBytes: args.issuerPriorPinFile ? (0, node_fs_1.readFileSync)(args.issuerPriorPinFile, 'utf8') : null,
|
|
881
|
+
priorRef,
|
|
882
|
+
controlJws: args.issuerControlFile ? (0, node_fs_1.readFileSync)(args.issuerControlFile, 'utf8').trim() : null,
|
|
883
|
+
required: true,
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
catch (err) {
|
|
887
|
+
console.error(`Error reading issuer pin: ${err instanceof Error ? err.message : String(err)}`);
|
|
888
|
+
return 3;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
const result = await (0, anchor_witness_js_1.verifyAnchoredRoot)({
|
|
892
|
+
receipt,
|
|
893
|
+
inclusion,
|
|
894
|
+
head,
|
|
895
|
+
baseRpcUrl: args.rpcUrl || undefined,
|
|
896
|
+
solanaRpcUrl: args.solanaRpcUrl || undefined,
|
|
897
|
+
checkNewestAnchor: args.sawRpc,
|
|
898
|
+
epochRecord,
|
|
899
|
+
verifyEpochSignature,
|
|
900
|
+
issuerPin,
|
|
901
|
+
jwks: loaded.jwks,
|
|
902
|
+
trustedKids,
|
|
903
|
+
issuerHistory: resolved.document,
|
|
904
|
+
strictIssuerHistory: false,
|
|
905
|
+
anchorWalletDocument: anchorWalletDocument,
|
|
906
|
+
});
|
|
907
|
+
let canonicalPreimage = null;
|
|
908
|
+
if (args.canonicalPreimageFile) {
|
|
909
|
+
canonicalPreimage = (0, node_fs_1.readFileSync)(args.canonicalPreimageFile, 'utf8');
|
|
910
|
+
}
|
|
911
|
+
const verified = await (0, index_js_1.verifyReceipt)(receipt, {
|
|
912
|
+
jwks: loaded.jwks,
|
|
913
|
+
jwksUri: args.jwksUrl || undefined,
|
|
914
|
+
fetchJwks: args.fetchJwks,
|
|
915
|
+
trustedKids,
|
|
916
|
+
checkNullifier: args.checkNullifier,
|
|
917
|
+
checkPayer: args.checkPayer,
|
|
918
|
+
rpcUrl: args.rpcUrl || undefined,
|
|
919
|
+
solanaRpcUrl: args.solanaRpcUrl || undefined,
|
|
920
|
+
requirePreimages: !args.noPreimage,
|
|
921
|
+
...historyOptions(resolved),
|
|
922
|
+
canonicalPreimage,
|
|
923
|
+
head,
|
|
924
|
+
});
|
|
925
|
+
// Inclusion was already checked above. Passing it again re-derives the leaf
|
|
926
|
+
// from book_chain.row_hash only, which rejects a receipt that stores row_hash
|
|
927
|
+
// at the top level. Log mode still requires the receipt itself. A missing or
|
|
928
|
+
// stripped issuer signature, a partial check, or a signed-versus-outer
|
|
929
|
+
// mismatch fails the combined result. An inclusion match is not VERIFIED.
|
|
930
|
+
const issuerSig = receipt.issuer_signature;
|
|
931
|
+
const signaturePresent = Boolean(issuerSig?.jws || issuerSig?.value);
|
|
932
|
+
const historyMiss = verified.issuer_history.reason === 'kid_not_in_history' || resolved.document == null;
|
|
933
|
+
const historyOnly = historyMiss
|
|
934
|
+
&& verified.claim_mismatches.length === 0
|
|
935
|
+
&& verified.errors.every((err) => /issuer history|kid_not_in_history/.test(err));
|
|
936
|
+
const receiptVerified = signaturePresent
|
|
937
|
+
&& verified.issuer_signature.valid === true
|
|
938
|
+
&& (verified.overall === 'verified' || verified.overall === 'verified_carried_forward' || historyOnly)
|
|
939
|
+
&& verified.claim_mismatches.length === 0;
|
|
940
|
+
const receiptFailed = !receiptVerified;
|
|
941
|
+
const receiptErrors = [...verified.errors];
|
|
942
|
+
if (!signaturePresent) {
|
|
943
|
+
receiptErrors.push(verified.issuer_signature.reason || 'receipt_signature_missing');
|
|
944
|
+
}
|
|
945
|
+
else if (verified.issuer_signature.valid !== true && verified.issuer_signature.reason) {
|
|
946
|
+
receiptErrors.push(verified.issuer_signature.reason);
|
|
947
|
+
}
|
|
948
|
+
else if (verified.overall === 'partial') {
|
|
949
|
+
receiptErrors.push(verified.issuer_signature.reason || 'receipt_check_partial');
|
|
950
|
+
}
|
|
951
|
+
const errors = receiptFailed
|
|
952
|
+
? [...result.errors, ...receiptErrors.filter((err) => !result.errors.includes(err))]
|
|
953
|
+
: result.errors;
|
|
954
|
+
const logMiss = result.overall === 'not_in_log';
|
|
955
|
+
const anchorOverall = logMiss
|
|
956
|
+
? 'not_in_log'
|
|
957
|
+
: receiptFailed
|
|
958
|
+
? 'failed'
|
|
959
|
+
: result.overall;
|
|
960
|
+
const combined = {
|
|
961
|
+
...result,
|
|
962
|
+
errors,
|
|
963
|
+
overall: anchorOverall,
|
|
964
|
+
receipt_lane: verified.receipt_lane,
|
|
965
|
+
receipt_check: {
|
|
966
|
+
overall: result.overall === 'pending' && !receiptFailed ? 'pending' : verified.overall,
|
|
967
|
+
verified_scope: verified.verified_scope,
|
|
968
|
+
claim_mismatches: verified.claim_mismatches,
|
|
969
|
+
unsigned_fields: verified.unsigned_fields,
|
|
970
|
+
errors: verified.errors,
|
|
971
|
+
},
|
|
972
|
+
};
|
|
973
|
+
const logState = anchorLogState(result);
|
|
974
|
+
const verdict = (0, verdict_js_1.buildVerifyVerdict)({
|
|
975
|
+
version: packageVersion(),
|
|
976
|
+
receiptId: verified.receipt_id,
|
|
977
|
+
displayId: displayIdOf(receipt),
|
|
978
|
+
payloadVersion: verified.receipt_version ?? null,
|
|
979
|
+
historySource: resolved.source,
|
|
980
|
+
historyHead: resolved.headHash,
|
|
981
|
+
historyCount: resolved.entryCount,
|
|
982
|
+
historyAsOf: resolved.asOf,
|
|
983
|
+
signatureValid: signaturePresent && verified.issuer_signature.valid === true,
|
|
984
|
+
signatureChecked: true,
|
|
985
|
+
historyChecked: verified.issuer_history.checked,
|
|
986
|
+
historyOk: verified.issuer_history.ok,
|
|
987
|
+
historyReason: resolved.invalid ? (resolved.reason || verified.issuer_history.reason) : verified.issuer_history.reason,
|
|
988
|
+
historyMissing: resolved.document == null && !resolved.invalid,
|
|
989
|
+
bindingExpected: !!verified.binding.expected,
|
|
990
|
+
bindingMatches: verified.binding.matches,
|
|
991
|
+
claimMismatches: verified.claim_mismatches.length,
|
|
992
|
+
payerChecked: verified.payer.checked,
|
|
993
|
+
payerValid: verified.payer.valid,
|
|
994
|
+
errors: errors,
|
|
995
|
+
logInclusion: logState.logInclusion,
|
|
996
|
+
anchor: logState.anchor,
|
|
997
|
+
logReason: logState.logReason,
|
|
998
|
+
log: {
|
|
999
|
+
leaf_index: inclusion.leaf_index ?? null,
|
|
1000
|
+
tree_size: inclusion.tree_size ?? null,
|
|
1001
|
+
root: result.root,
|
|
1002
|
+
anchors: {
|
|
1003
|
+
base: result.base.tx,
|
|
1004
|
+
solana: result.solana.signature,
|
|
1005
|
+
},
|
|
1006
|
+
},
|
|
1007
|
+
});
|
|
1008
|
+
if (args.json) {
|
|
1009
|
+
return printVerdict(true, verdict, combined, anchorOverall);
|
|
1010
|
+
}
|
|
1011
|
+
printAnchor(combined, false, args.quiet, (0, verdict_js_1.overallLine)(verdict, anchorOverall));
|
|
1012
|
+
if (!args.quiet || receiptFailed || verdict.result !== 'verified') {
|
|
1013
|
+
const anchorPending = result.overall === 'pending';
|
|
1014
|
+
const receiptLabel = anchorPending && !receiptFailed
|
|
1015
|
+
? 'PENDING'
|
|
1016
|
+
: (0, verdict_js_1.overallLine)(verdict, verified.overall);
|
|
1017
|
+
console.log(` Receipt checks: ${receiptLabel} (signed claims only)`);
|
|
1018
|
+
if (verified.claim_mismatches.length > 0) {
|
|
1019
|
+
for (const mismatch of verified.claim_mismatches) {
|
|
1020
|
+
console.log(` ${mismatch.field}: outer ${mismatch.outer} ≠ signed ${mismatch.signed}`);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (!(anchorPending && !receiptFailed))
|
|
1024
|
+
printUnsigned(verified.unsigned_fields);
|
|
1025
|
+
console.log('');
|
|
1026
|
+
}
|
|
1027
|
+
if (!args.quiet)
|
|
1028
|
+
printLane(verified.receipt_lane);
|
|
1029
|
+
console.log(` ${verdict.message}`);
|
|
1030
|
+
if (verdict.history.source === 'bundled' && verdict.history.as_of) {
|
|
1031
|
+
console.log(` Issuer history: as of ${verdict.history.as_of}`);
|
|
1032
|
+
}
|
|
1033
|
+
return verdict.exit_code;
|
|
1034
|
+
}
|
|
1035
|
+
function anchorLogState(result) {
|
|
1036
|
+
if (result.overall === 'not_in_log') {
|
|
1037
|
+
return { logInclusion: 'fail', anchor: 'pass', logReason: 'not_in_log' };
|
|
1038
|
+
}
|
|
1039
|
+
if (result.overall === 'pending' || result.overall === 'partial') {
|
|
1040
|
+
return {
|
|
1041
|
+
logInclusion: result.inclusion.valid ? 'pass' : 'could_not_check',
|
|
1042
|
+
anchor: 'could_not_check',
|
|
1043
|
+
logReason: result.overall === 'pending' ? 'pending_anchor' : (result.errors[0] || 'partial'),
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
if (result.inclusion && result.inclusion.valid === false) {
|
|
1047
|
+
return {
|
|
1048
|
+
logInclusion: 'fail',
|
|
1049
|
+
anchor: 'fail',
|
|
1050
|
+
logReason: result.errors[0] || result.inclusion.reason || 'log_inclusion',
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
if (result.overall === 'failed') {
|
|
1054
|
+
return { logInclusion: 'pass', anchor: 'fail', logReason: result.errors[0] || 'anchor' };
|
|
1055
|
+
}
|
|
1056
|
+
if (result.overall === 'verified')
|
|
1057
|
+
return { logInclusion: 'pass', anchor: 'pass', logReason: null };
|
|
1058
|
+
return { logInclusion: 'could_not_check', anchor: 'could_not_check', logReason: 'log_unreachable' };
|
|
1059
|
+
}
|
|
1060
|
+
function displayIdOf(receipt) {
|
|
1061
|
+
if (typeof receipt.display_id === 'string' && receipt.display_id)
|
|
1062
|
+
return receipt.display_id;
|
|
1063
|
+
const id = receipt.task_id || receipt.receipt_id || '';
|
|
1064
|
+
const short = id.match(/^(chit-[0-9a-f]{8})/i);
|
|
1065
|
+
return short ? short[1] : (id || null);
|
|
1066
|
+
}
|
|
1067
|
+
function printUnsigned(fields) {
|
|
1068
|
+
console.log(' Scope: signed claims only. Unsigned fields are UNVERIFIED.');
|
|
1069
|
+
if (fields.length === 0)
|
|
1070
|
+
return;
|
|
1071
|
+
console.log(` Unsigned fields (${fields.length})`);
|
|
1072
|
+
console.log(' ─────────────────────────────────────────────────');
|
|
1073
|
+
for (const field of fields)
|
|
1074
|
+
console.log(` UNVERIFIED ${field}`);
|
|
1075
|
+
}
|
|
1076
|
+
async function runRefusal(doc, args) {
|
|
1077
|
+
const trustedKids = args.trustedKids ?? index_js_1.DEFAULT_TRUSTED_ISSUER_KIDS;
|
|
1078
|
+
const refusalGate = (0, receipt_version_js_1.classifyReceiptDocument)(doc);
|
|
1079
|
+
if (!refusalGate.ok)
|
|
1080
|
+
return emitVersionFailure(args, refusalGate.errors);
|
|
1081
|
+
const result = (0, index_js_1.verifyRefusal)(doc, {
|
|
1082
|
+
jwks: args.jwks,
|
|
1083
|
+
trustedKids,
|
|
1084
|
+
});
|
|
1085
|
+
if (!result.valid && (result.errors[0] === 'unsupported_version' || result.reason === 'unsupported_version' || result.reason === 'payload_version_mismatch')) {
|
|
1086
|
+
return emitVersionFailure(args, result.errors.length ? result.errors : [result.reason || 'unsupported_version']);
|
|
1087
|
+
}
|
|
1088
|
+
const preimages = await (0, preimage_js_1.verifyPublishedPreimages)(doc, {
|
|
1089
|
+
requirePreimages: args.requirePreimages,
|
|
1090
|
+
});
|
|
1091
|
+
let canonicalFailed = false;
|
|
1092
|
+
if (args.canonicalPreimage != null) {
|
|
1093
|
+
const canonical = (0, canonical_preimage_js_1.verifyCanonicalPreimageBytes)(args.canonicalPreimage, result.payload_hash);
|
|
1094
|
+
canonicalFailed = !canonical.ok;
|
|
1095
|
+
if (canonicalFailed)
|
|
1096
|
+
preimages.errors.push(`canonical preimage: ${canonical.reason}`);
|
|
1097
|
+
if (canonicalFailed)
|
|
1098
|
+
preimages.ok = false;
|
|
1099
|
+
}
|
|
1100
|
+
const pin = result.valid ? (0, issuer_history_js_1.readIssuerHistoryPin)({
|
|
1101
|
+
issuer_history: result.issuer_history,
|
|
1102
|
+
}) : null;
|
|
1103
|
+
const history = await (0, issuer_history_js_1.checkReceiptIssuerHistory)(doc, {
|
|
1104
|
+
document: args.resolved.document,
|
|
1105
|
+
documentBytes: args.resolved.bytes,
|
|
1106
|
+
fetchHistory: false,
|
|
1107
|
+
strict: false,
|
|
1108
|
+
jwks: args.jwks,
|
|
1109
|
+
trustedKids,
|
|
1110
|
+
issuedAt: doc.issued_at ?? null,
|
|
1111
|
+
kid: result.kid ?? doc.issuer_signature?.kid ?? null,
|
|
1112
|
+
pin,
|
|
1113
|
+
requirePin: result.valid && Number(result.payload_version) >= 2 && args.resolved.document != null,
|
|
1114
|
+
});
|
|
1115
|
+
const historyFailed = history.checked && !history.ok && history.reason !== 'kid_not_in_history';
|
|
1116
|
+
const failed = !result.valid || !preimages.ok || historyFailed || canonicalFailed;
|
|
1117
|
+
const verdict = (0, verdict_js_1.buildVerifyVerdict)({
|
|
1118
|
+
version: packageVersion(),
|
|
1119
|
+
receiptId: result.refusal_id || doc.refusal_id || null,
|
|
1120
|
+
displayId: result.refusal_id || doc.refusal_id || null,
|
|
1121
|
+
payloadVersion: result.payload_version ?? null,
|
|
1122
|
+
historySource: args.resolved.source,
|
|
1123
|
+
historyHead: args.resolved.headHash,
|
|
1124
|
+
historyCount: args.resolved.entryCount,
|
|
1125
|
+
historyAsOf: args.resolved.asOf,
|
|
1126
|
+
signatureValid: result.valid,
|
|
1127
|
+
signatureChecked: true,
|
|
1128
|
+
historyChecked: history.checked,
|
|
1129
|
+
historyOk: history.ok,
|
|
1130
|
+
historyReason: history.reason || args.resolved.reason,
|
|
1131
|
+
historyMissing: args.resolved.document == null,
|
|
1132
|
+
errors: [...result.errors, ...preimages.errors],
|
|
1133
|
+
logInclusion: 'unclaimed',
|
|
1134
|
+
anchor: 'unclaimed',
|
|
1135
|
+
});
|
|
1136
|
+
const overall = verdict.result === 'verified' ? 'verified' : verdict.result === 'could_not_check' ? 'could_not_check' : 'failed';
|
|
1137
|
+
if (args.json) {
|
|
1138
|
+
return printVerdict(true, verdict, { ...result, preimages, issuer_history: history, overall }, overall);
|
|
1139
|
+
}
|
|
1140
|
+
else if (!args.quiet || failed || verdict.result !== 'verified') {
|
|
1141
|
+
console.log('');
|
|
1142
|
+
console.log(' Chit402 Refusal Verification');
|
|
1143
|
+
console.log(' ─────────────────────────────────────────────────');
|
|
1144
|
+
console.log(` Schema: ${result.schema}`);
|
|
1145
|
+
console.log(` Refusal ID: ${result.refusal_id || doc.refusal_id || '—'}`);
|
|
1146
|
+
console.log(` Code: ${result.refusal_code || doc.refusal_code || '—'}`);
|
|
1147
|
+
console.log(` Nonce: ${result.nonce || '—'}`);
|
|
1148
|
+
console.log(` Chain: ${result.chain_id ?? 'null'}`);
|
|
1149
|
+
console.log(` Charged: false`);
|
|
1150
|
+
console.log('');
|
|
1151
|
+
console.log(' Issuer Signature');
|
|
1152
|
+
console.log(' ─────────────────────────────────────────────────');
|
|
1153
|
+
console.log(` Valid: ${result.valid ? '✓ YES' : '✗ NO'}`);
|
|
1154
|
+
if (result.kid)
|
|
1155
|
+
console.log(` Kid: ${result.kid}`);
|
|
1156
|
+
if (!result.valid && result.reason)
|
|
1157
|
+
console.log(` Reason: ${result.reason}`);
|
|
1158
|
+
console.log('');
|
|
1159
|
+
if (!failed) {
|
|
1160
|
+
console.log(' What this proves');
|
|
1161
|
+
console.log(' ─────────────────────────────────────────────────');
|
|
1162
|
+
for (const line of result.proves)
|
|
1163
|
+
console.log(` ${line}`);
|
|
1164
|
+
console.log('');
|
|
1165
|
+
}
|
|
1166
|
+
console.log(' What this does not prove');
|
|
1167
|
+
console.log(' ─────────────────────────────────────────────────');
|
|
1168
|
+
for (const line of result.does_not_prove)
|
|
1169
|
+
console.log(` ${line}`);
|
|
1170
|
+
console.log('');
|
|
1171
|
+
console.log(` Overall: ${(0, verdict_js_1.overallLine)(verdict, overall)}`);
|
|
1172
|
+
console.log(` ${verdict.message}`);
|
|
1173
|
+
if (!preimages.ok)
|
|
1174
|
+
console.log(` Preimages: ${preimages.errors.join('; ')}`);
|
|
1175
|
+
if (history.warning)
|
|
1176
|
+
console.log(` Issuer history: ${history.warning}`);
|
|
1177
|
+
if (historyFailed && history.reason)
|
|
1178
|
+
console.log(` Issuer history: ${history.reason}`);
|
|
1179
|
+
console.log('');
|
|
1180
|
+
}
|
|
1181
|
+
return verdict.exit_code;
|
|
1182
|
+
}
|
|
1183
|
+
async function legacyMain() {
|
|
1184
|
+
const removed = removedFlag(process.argv.slice(2));
|
|
1185
|
+
if (removed) {
|
|
1186
|
+
console.error(removed);
|
|
1187
|
+
return 1;
|
|
1188
|
+
}
|
|
1189
|
+
const args = parseArgs(process.argv.slice(2));
|
|
1190
|
+
if (args.version) {
|
|
1191
|
+
console.log(`@xfuel/verify ${packageVersion()}`);
|
|
1192
|
+
console.log((0, receipt_version_js_1.formatSupportedReceiptVersions)());
|
|
1193
|
+
return 0;
|
|
1194
|
+
}
|
|
1195
|
+
let anchorMode = args.anchorFlag
|
|
1196
|
+
|| Boolean(args.inclusionFile || args.headFile)
|
|
1197
|
+
|| (args.positionals.length >= 3 && args.sawRpc);
|
|
1198
|
+
if (!anchorMode && args.sawRpc && args.positionals.some((file) => fileLooksLikeHead(file))) {
|
|
1199
|
+
anchorMode = true;
|
|
1200
|
+
}
|
|
1201
|
+
if (args.help) {
|
|
1202
|
+
console.log(HELP);
|
|
1203
|
+
return 0;
|
|
1204
|
+
}
|
|
1205
|
+
if (args.url && !args.jwsFile) {
|
|
1206
|
+
try {
|
|
1207
|
+
const response = await fetch(args.url);
|
|
1208
|
+
await response.arrayBuffer();
|
|
1209
|
+
}
|
|
1210
|
+
catch {
|
|
1211
|
+
// A failed fetch is still not a pass.
|
|
1212
|
+
}
|
|
1213
|
+
console.log('owner_proof_required');
|
|
1214
|
+
console.log(' Overall: OWNER_PROOF_REQUIRED');
|
|
1215
|
+
return 1;
|
|
1216
|
+
}
|
|
1217
|
+
let offlineWitness = null;
|
|
1218
|
+
if (!anchorMode) {
|
|
1219
|
+
try {
|
|
1220
|
+
offlineWitness = readOfflineWitness(args);
|
|
1221
|
+
}
|
|
1222
|
+
catch (err) {
|
|
1223
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
1224
|
+
return 3;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
// A shell plus a holder is not anchor mode. Inclusion is checked with the
|
|
1228
|
+
// holder. Anchor mode would verify the unsigned shell and skip the key.
|
|
1229
|
+
if (anchorMode && !args.jwsFile && !args.acceptShell)
|
|
1230
|
+
return runAnchor(args);
|
|
1231
|
+
if (!args.file) {
|
|
1232
|
+
console.log(HELP);
|
|
1233
|
+
return 3;
|
|
1234
|
+
}
|
|
1235
|
+
let receipt;
|
|
1236
|
+
try {
|
|
1237
|
+
receipt = readReceipt(args.file);
|
|
1238
|
+
}
|
|
1239
|
+
catch (err) {
|
|
1240
|
+
console.error(`Error reading receipt: ${err instanceof Error ? err.message : String(err)}`);
|
|
1241
|
+
return 3;
|
|
1242
|
+
}
|
|
1243
|
+
if ((0, shell_js_1.isReceiptShell)(receipt) || args.acceptShell || args.jwsFile) {
|
|
1244
|
+
if (!(0, shell_js_1.isReceiptShell)(receipt)) {
|
|
1245
|
+
console.error('Invalid shell: schema is not chit402.receipt_shell.v1');
|
|
1246
|
+
return 3;
|
|
1247
|
+
}
|
|
1248
|
+
if (!args.jwsFile) {
|
|
1249
|
+
const shellGate = (0, receipt_version_js_1.classifyShellDocument)(receipt);
|
|
1250
|
+
if (!shellGate.ok)
|
|
1251
|
+
return emitVersionFailure(args, shellGate.errors);
|
|
1252
|
+
console.log(shell_js_1.INCLUDED_SHELL_LINE);
|
|
1253
|
+
console.log(' Overall: INCLUDED_SHELL');
|
|
1254
|
+
return args.acceptShell ? 0 : 1;
|
|
1255
|
+
}
|
|
1256
|
+
let holder;
|
|
1257
|
+
try {
|
|
1258
|
+
const text = (0, node_fs_1.readFileSync)(args.jwsFile, 'utf8');
|
|
1259
|
+
let parsed = text;
|
|
1260
|
+
try {
|
|
1261
|
+
parsed = JSON.parse(text);
|
|
1262
|
+
}
|
|
1263
|
+
catch {
|
|
1264
|
+
parsed = text;
|
|
1265
|
+
}
|
|
1266
|
+
holder = (0, shell_js_1.holderDocumentFromOwnerView)(parsed);
|
|
1267
|
+
}
|
|
1268
|
+
catch (err) {
|
|
1269
|
+
console.error(`Error reading JWS: ${err instanceof Error ? err.message : String(err)}`);
|
|
1270
|
+
return 3;
|
|
1271
|
+
}
|
|
1272
|
+
const shellDoc = receipt;
|
|
1273
|
+
const holderGate = (0, receipt_version_js_1.classifyReceiptDocument)(holder);
|
|
1274
|
+
if (!holderGate.ok)
|
|
1275
|
+
return emitVersionFailure(args, holderGate.errors);
|
|
1276
|
+
const compared = (0, shell_js_1.compareShellToJws)(shellDoc, holder);
|
|
1277
|
+
if (!compared.ok) {
|
|
1278
|
+
console.log('shell_jws_mismatch');
|
|
1279
|
+
if (compared.field)
|
|
1280
|
+
console.log(` Field: ${compared.field}`);
|
|
1281
|
+
console.log(' Overall: FAILED');
|
|
1282
|
+
return 1;
|
|
1283
|
+
}
|
|
1284
|
+
let jwks;
|
|
1285
|
+
if (args.jwksFile) {
|
|
1286
|
+
try {
|
|
1287
|
+
jwks = JSON.parse((0, node_fs_1.readFileSync)(args.jwksFile, 'utf8'));
|
|
1288
|
+
}
|
|
1289
|
+
catch (err) {
|
|
1290
|
+
console.error(`Error reading JWKS file: ${err instanceof Error ? err.message : String(err)}`);
|
|
1291
|
+
return 3;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
let inclusion = offlineWitness?.inclusion ?? null;
|
|
1295
|
+
let head = offlineWitness?.head ?? null;
|
|
1296
|
+
try {
|
|
1297
|
+
if (args.inclusionFile)
|
|
1298
|
+
inclusion = readJson(args.inclusionFile);
|
|
1299
|
+
if (args.headFile)
|
|
1300
|
+
head = readJson(args.headFile);
|
|
1301
|
+
}
|
|
1302
|
+
catch (err) {
|
|
1303
|
+
console.error(`Error reading inclusion: ${err instanceof Error ? err.message : String(err)}`);
|
|
1304
|
+
return 3;
|
|
1305
|
+
}
|
|
1306
|
+
const trustedKids = args.noTrustedKid
|
|
1307
|
+
? []
|
|
1308
|
+
: (args.trustedKids ?? [...index_js_1.DEFAULT_TRUSTED_ISSUER_KIDS]);
|
|
1309
|
+
let shellResolved;
|
|
1310
|
+
try {
|
|
1311
|
+
shellResolved = await resolvedHistory(args, holder, jwks, trustedKids);
|
|
1312
|
+
}
|
|
1313
|
+
catch (err) {
|
|
1314
|
+
console.error(`Error reading issuer history: ${err instanceof Error ? err.message : String(err)}`);
|
|
1315
|
+
return 3;
|
|
1316
|
+
}
|
|
1317
|
+
const result = await (0, index_js_1.verifyReceipt)(holder, {
|
|
1318
|
+
jwks,
|
|
1319
|
+
jwksUri: args.jwksUrl || undefined,
|
|
1320
|
+
fetchJwks: args.fetchJwks,
|
|
1321
|
+
trustedKids,
|
|
1322
|
+
checkPayer: args.checkPayer,
|
|
1323
|
+
rpcUrl: args.rpcUrl || undefined,
|
|
1324
|
+
solanaRpcUrl: args.solanaRpcUrl || undefined,
|
|
1325
|
+
requirePreimages: false,
|
|
1326
|
+
...historyOptions(shellResolved),
|
|
1327
|
+
salt: args.salt,
|
|
1328
|
+
head: head ?? undefined,
|
|
1329
|
+
inclusion: inclusion ?? undefined,
|
|
1330
|
+
});
|
|
1331
|
+
const inclusionErrors = result.errors.filter((err) => /inclusion|leaf|row_hash|bad_root|tree_size|tree_head|no_leaf/.test(err));
|
|
1332
|
+
const shellLeaf = (shellDoc.inclusion && typeof shellDoc.inclusion === 'object')
|
|
1333
|
+
? shellDoc.inclusion.leaf_hash
|
|
1334
|
+
: null;
|
|
1335
|
+
const provedLeaf = inclusion && typeof inclusion.leaf === 'string'
|
|
1336
|
+
? inclusion.leaf
|
|
1337
|
+
: null;
|
|
1338
|
+
// A head the user wrote by hand ({root, tree_size}) is not a log. The
|
|
1339
|
+
// inclusion only counts against a head that carries an issuer JWS;
|
|
1340
|
+
// verifyReceipt() checks that JWS against the same trust set.
|
|
1341
|
+
const headSig = head && typeof head === 'object'
|
|
1342
|
+
? head.issuer_signature
|
|
1343
|
+
: null;
|
|
1344
|
+
const headSigned = !!headSig && typeof headSig.jws === 'string' && headSig.jws.split('.').length === 3;
|
|
1345
|
+
const verdict = (0, shell_js_1.shellHolderVerdict)({
|
|
1346
|
+
signatureValid: result.issuer_signature.valid === true,
|
|
1347
|
+
keyTrusted: result.issuer_signature.key_trusted === true,
|
|
1348
|
+
signatureReason: result.issuer_signature.reason || (result.issuer_signature.key_trusted ? null : 'key untrusted'),
|
|
1349
|
+
inclusionSupplied: inclusion != null,
|
|
1350
|
+
inclusionOk: inclusion != null && inclusionErrors.length === 0,
|
|
1351
|
+
inclusionReason: inclusion == null ? 'inclusion_missing' : (inclusionErrors[0] || null),
|
|
1352
|
+
headSigned,
|
|
1353
|
+
paymentRef: result.tx,
|
|
1354
|
+
bindingExpected: !!result.binding.expected,
|
|
1355
|
+
bindingMatches: result.binding.matches === true,
|
|
1356
|
+
shellLeaf: shellLeaf == null ? null : String(shellLeaf),
|
|
1357
|
+
provedLeaf,
|
|
1358
|
+
});
|
|
1359
|
+
const coveringMiss = result.issuer_history.reason === 'kid_not_in_history' || shellResolved.document == null;
|
|
1360
|
+
const receiptOk = result.issuer_signature.valid === true
|
|
1361
|
+
&& result.claim_mismatches.length === 0
|
|
1362
|
+
&& (result.overall === 'verified' || result.overall === 'verified_carried_forward' || coveringMiss);
|
|
1363
|
+
if (!verdict.verified || !receiptOk) {
|
|
1364
|
+
const reason = verdict.reason || result.errors[0] || 'failed';
|
|
1365
|
+
console.log(reason);
|
|
1366
|
+
console.log(' Overall: FAILED');
|
|
1367
|
+
return 1;
|
|
1368
|
+
}
|
|
1369
|
+
if (coveringMiss || (result.issuer_history.checked && !result.issuer_history.ok)) {
|
|
1370
|
+
if (coveringMiss) {
|
|
1371
|
+
console.log(' Overall: COULD_NOT_CHECK');
|
|
1372
|
+
console.log(' No issuer history covers this key.');
|
|
1373
|
+
console.log(' Next: Pass --issuer-history with a snapshot that includes this kid.');
|
|
1374
|
+
return 2;
|
|
1375
|
+
}
|
|
1376
|
+
console.log(result.issuer_history.reason || 'key_window');
|
|
1377
|
+
console.log(' Overall: INVALID');
|
|
1378
|
+
return 1;
|
|
1379
|
+
}
|
|
1380
|
+
if (args.checkPayer && !result.payer.valid) {
|
|
1381
|
+
console.log(result.payer.reason || 'payment_unchecked');
|
|
1382
|
+
console.log(' Overall: FAILED');
|
|
1383
|
+
return 1;
|
|
1384
|
+
}
|
|
1385
|
+
const issuer = holder.issuer_signature;
|
|
1386
|
+
if (args.salt) {
|
|
1387
|
+
const opened = (0, shell_js_1.openV11Commitment)({
|
|
1388
|
+
payload: (0, shell_js_1.decodeJwsPayload)(issuer?.jws),
|
|
1389
|
+
saltHex: args.salt || '',
|
|
1390
|
+
body: Buffer.alloc(0),
|
|
1391
|
+
});
|
|
1392
|
+
if (!opened.ok) {
|
|
1393
|
+
console.log(opened.reason || 'commitment_mismatch');
|
|
1394
|
+
console.log(' Overall: FAILED');
|
|
1395
|
+
return 1;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
console.log(' Overall: VERIFIED');
|
|
1399
|
+
console.log(' Every check passed.');
|
|
1400
|
+
return 0;
|
|
1401
|
+
}
|
|
1402
|
+
if (typeof receipt.issuer_signature?.jws === 'string') {
|
|
1403
|
+
const receiptGate = (0, receipt_version_js_1.classifyReceiptDocument)(receipt);
|
|
1404
|
+
if (!receiptGate.ok)
|
|
1405
|
+
return emitVersionFailure(args, receiptGate.errors);
|
|
1406
|
+
}
|
|
1407
|
+
const v11 = receipt.v === 11 || (receipt.issuer_signature?.jws
|
|
1408
|
+
? (() => {
|
|
1409
|
+
try {
|
|
1410
|
+
const part = String(receipt.issuer_signature.jws).split('.')[1];
|
|
1411
|
+
const payload = JSON.parse(Buffer.from(part, 'base64url').toString('utf8'));
|
|
1412
|
+
return payload?.v === 11;
|
|
1413
|
+
}
|
|
1414
|
+
catch {
|
|
1415
|
+
return false;
|
|
1416
|
+
}
|
|
1417
|
+
})()
|
|
1418
|
+
: false);
|
|
1419
|
+
if (!receipt.task_id && !v11) {
|
|
1420
|
+
console.error('Invalid receipt: missing task_id');
|
|
1421
|
+
return 3;
|
|
1422
|
+
}
|
|
1423
|
+
// Load JWKS if provided
|
|
1424
|
+
let jwks;
|
|
1425
|
+
if (args.jwksFile) {
|
|
1426
|
+
try {
|
|
1427
|
+
const jwksContent = (0, node_fs_1.readFileSync)(args.jwksFile, 'utf8');
|
|
1428
|
+
jwks = JSON.parse(jwksContent);
|
|
1429
|
+
}
|
|
1430
|
+
catch (err) {
|
|
1431
|
+
console.error(`Error reading JWKS file: ${err instanceof Error ? err.message : String(err)}`);
|
|
1432
|
+
return 3;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
const trustedKids = args.noTrustedKid
|
|
1436
|
+
? []
|
|
1437
|
+
: (args.trustedKids ?? index_js_1.DEFAULT_TRUSTED_ISSUER_KIDS);
|
|
1438
|
+
let resolved;
|
|
1439
|
+
try {
|
|
1440
|
+
resolved = await resolvedHistory(args, receipt, jwks, trustedKids);
|
|
1441
|
+
}
|
|
1442
|
+
catch (err) {
|
|
1443
|
+
console.error(`Error reading issuer history: ${err instanceof Error ? err.message : String(err)}`);
|
|
1444
|
+
return 3;
|
|
1445
|
+
}
|
|
1446
|
+
if ((0, index_js_1.isRefusalDocument)(receipt)) {
|
|
1447
|
+
let canonicalPreimage = null;
|
|
1448
|
+
if (args.canonicalPreimageFile) {
|
|
1449
|
+
canonicalPreimage = (0, node_fs_1.readFileSync)(args.canonicalPreimageFile, 'utf8');
|
|
1450
|
+
}
|
|
1451
|
+
return runRefusal(receipt, {
|
|
1452
|
+
jwks,
|
|
1453
|
+
trustedKids,
|
|
1454
|
+
json: args.json,
|
|
1455
|
+
quiet: args.quiet,
|
|
1456
|
+
requirePreimages: !args.noPreimage,
|
|
1457
|
+
resolved,
|
|
1458
|
+
canonicalPreimage,
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
let canonicalPreimage = null;
|
|
1462
|
+
if (args.canonicalPreimageFile) {
|
|
1463
|
+
try {
|
|
1464
|
+
canonicalPreimage = (0, node_fs_1.readFileSync)(args.canonicalPreimageFile, 'utf8');
|
|
1465
|
+
}
|
|
1466
|
+
catch (err) {
|
|
1467
|
+
console.error(`Error reading canonical preimage: ${err instanceof Error ? err.message : String(err)}`);
|
|
1468
|
+
return 3;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
const carryAsked = args.carryOldHeadFile || args.carryOldInclusionFile
|
|
1472
|
+
|| args.carryHeadFile || args.carryInclusionFiles.length > 0;
|
|
1473
|
+
let carry = null;
|
|
1474
|
+
if (carryAsked) {
|
|
1475
|
+
try {
|
|
1476
|
+
carry = {
|
|
1477
|
+
oldHead: args.carryOldHeadFile ? readJson(args.carryOldHeadFile) : {},
|
|
1478
|
+
oldInclusion: args.carryOldInclusionFile ? readJson(args.carryOldInclusionFile) : { error: 'not_in_tree' },
|
|
1479
|
+
currentHead: args.carryHeadFile ? readJson(args.carryHeadFile) : {},
|
|
1480
|
+
currentInclusions: args.carryInclusionFiles.length > 0
|
|
1481
|
+
? args.carryInclusionFiles.map((file) => readJson(file))
|
|
1482
|
+
: [{ error: 'not_in_tree' }],
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
catch (err) {
|
|
1486
|
+
console.error(`Error reading carry-forward files: ${err instanceof Error ? err.message : String(err)}`);
|
|
1487
|
+
return 3;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
let open;
|
|
1491
|
+
if (args.open.length > 0) {
|
|
1492
|
+
open = {};
|
|
1493
|
+
try {
|
|
1494
|
+
for (const spec of args.open) {
|
|
1495
|
+
const opened = (0, v11_receipt_js_1.readOpenFile)(spec);
|
|
1496
|
+
if (opened.kind === 'output')
|
|
1497
|
+
open.output = opened.bytes;
|
|
1498
|
+
else
|
|
1499
|
+
open[opened.kind] = opened.text;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
catch (err) {
|
|
1503
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
1504
|
+
return 3;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
const result = await (0, index_js_1.verifyReceipt)(receipt, {
|
|
1508
|
+
jwks,
|
|
1509
|
+
jwksUri: args.jwksUrl || undefined,
|
|
1510
|
+
fetchJwks: args.fetchJwks,
|
|
1511
|
+
trustedKids,
|
|
1512
|
+
checkNullifier: args.checkNullifier,
|
|
1513
|
+
checkPayer: args.checkPayer,
|
|
1514
|
+
rpcUrl: args.rpcUrl || undefined,
|
|
1515
|
+
solanaRpcUrl: args.solanaRpcUrl || undefined,
|
|
1516
|
+
requirePreimages: !args.noPreimage,
|
|
1517
|
+
...historyOptions(resolved),
|
|
1518
|
+
canonicalPreimage,
|
|
1519
|
+
salt: args.salt,
|
|
1520
|
+
open,
|
|
1521
|
+
head: offlineWitness?.head ?? undefined,
|
|
1522
|
+
inclusion: offlineWitness?.inclusion ?? undefined,
|
|
1523
|
+
carry,
|
|
1524
|
+
});
|
|
1525
|
+
const hadLog = Boolean(offlineWitness?.inclusion || offlineWitness?.head);
|
|
1526
|
+
let logInclusion = 'unclaimed';
|
|
1527
|
+
let anchorState = 'unclaimed';
|
|
1528
|
+
let logReason = null;
|
|
1529
|
+
const carried = result.overall === 'verified_carried_forward';
|
|
1530
|
+
const headErrors = result.errors.filter((err) => /head_signature|head_claims|tree_head_mismatch|tree_size_mismatch|inclusion|not_in_tree|bad_root|no_leaf/.test(err));
|
|
1531
|
+
if (hadLog || headErrors.length) {
|
|
1532
|
+
if (headErrors.some((err) => err.includes('not_in_tree')) && headErrors.length === 1) {
|
|
1533
|
+
logInclusion = 'fail';
|
|
1534
|
+
anchorState = 'pass';
|
|
1535
|
+
logReason = 'not_in_log';
|
|
1536
|
+
}
|
|
1537
|
+
else if (headErrors.length) {
|
|
1538
|
+
logInclusion = headErrors.some((err) => /inclusion|not_in_tree|bad_root|no_leaf/.test(err)) ? 'fail' : 'pass';
|
|
1539
|
+
anchorState = 'fail';
|
|
1540
|
+
logReason = headErrors[0];
|
|
1541
|
+
}
|
|
1542
|
+
else {
|
|
1543
|
+
logInclusion = 'pass';
|
|
1544
|
+
anchorState = 'pass';
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
else if (!carried && typeof receipt.verify_url === 'string' && historyHostAllowed(receipt.verify_url)) {
|
|
1548
|
+
const fresh = await freshPublicLog(receipt, resolved.document);
|
|
1549
|
+
logInclusion = fresh.logInclusion;
|
|
1550
|
+
anchorState = fresh.anchor;
|
|
1551
|
+
logReason = fresh.logReason;
|
|
1552
|
+
}
|
|
1553
|
+
const receiptVerdict = (0, verdict_js_1.buildVerifyVerdict)({
|
|
1554
|
+
version: packageVersion(),
|
|
1555
|
+
receiptId: result.receipt_id,
|
|
1556
|
+
displayId: displayIdOf(receipt),
|
|
1557
|
+
payloadVersion: result.receipt_version ?? null,
|
|
1558
|
+
historySource: resolved.source,
|
|
1559
|
+
historyHead: resolved.headHash,
|
|
1560
|
+
historyCount: resolved.entryCount,
|
|
1561
|
+
historyAsOf: resolved.asOf,
|
|
1562
|
+
signatureValid: result.issuer_signature.valid === true || carried,
|
|
1563
|
+
signatureChecked: result.issuer_signature.checked || carried,
|
|
1564
|
+
historyChecked: result.issuer_history.checked || carried,
|
|
1565
|
+
historyOk: carried ? true : result.issuer_history.ok,
|
|
1566
|
+
historyReason: carried ? null : (resolved.invalid ? (resolved.reason || result.issuer_history.reason) : result.issuer_history.reason),
|
|
1567
|
+
historyMissing: carried ? false : (resolved.document == null && !resolved.invalid),
|
|
1568
|
+
bindingExpected: !!result.binding.expected,
|
|
1569
|
+
bindingMatches: result.binding.matches,
|
|
1570
|
+
claimMismatches: result.claim_mismatches.length,
|
|
1571
|
+
payerChecked: result.payer.checked,
|
|
1572
|
+
payerValid: result.payer.valid,
|
|
1573
|
+
errors: result.errors,
|
|
1574
|
+
logInclusion,
|
|
1575
|
+
anchor: anchorState,
|
|
1576
|
+
logReason,
|
|
1577
|
+
log: null,
|
|
1578
|
+
});
|
|
1579
|
+
if (args.json) {
|
|
1580
|
+
return printVerdict(true, receiptVerdict, result, result.overall);
|
|
1581
|
+
}
|
|
1582
|
+
else if (!args.quiet || result.overall === 'failed' || receiptVerdict.result !== 'verified') {
|
|
1583
|
+
console.log('');
|
|
1584
|
+
console.log(` Chit402 Receipt Verification`);
|
|
1585
|
+
console.log(` ─────────────────────────────────────────────────`);
|
|
1586
|
+
console.log(` Receipt ID: ${result.receipt_id}`);
|
|
1587
|
+
console.log(` Hub: ${result.hub || '—'}`);
|
|
1588
|
+
console.log(` Model: ${result.model || '—'}`);
|
|
1589
|
+
console.log(` Amount: ${result.issuer_signature.valid ? formatAmount(result.amount_usdc) : '— (not verified)'}`);
|
|
1590
|
+
console.log(` TX: ${result.tx || '—'}`);
|
|
1591
|
+
console.log(` Output Hash: ${result.output_hash ? result.output_hash.slice(0, 18) + '…' : '—'}`);
|
|
1592
|
+
console.log('');
|
|
1593
|
+
console.log(` Binding`);
|
|
1594
|
+
console.log(` ─────────────────────────────────────────────────`);
|
|
1595
|
+
if (result.binding.expected) {
|
|
1596
|
+
console.log(` Expected: ${result.binding.expected.slice(0, 18)}…`);
|
|
1597
|
+
console.log(` Recomputed: ${result.binding.recomputed?.slice(0, 18)}…`);
|
|
1598
|
+
console.log(` Match: ${result.binding.matches ? '✓ YES' : '✗ NO'}`);
|
|
1599
|
+
console.log(` Covers: ${result.binding.covers.join(', ')}`);
|
|
1600
|
+
}
|
|
1601
|
+
else {
|
|
1602
|
+
console.log(` Status: ${result.binding.reason || 'No payment-binding commitment'}`);
|
|
1603
|
+
}
|
|
1604
|
+
console.log('');
|
|
1605
|
+
console.log(` Issuer Signature`);
|
|
1606
|
+
console.log(` ─────────────────────────────────────────────────`);
|
|
1607
|
+
if (result.issuer_signature.checked) {
|
|
1608
|
+
console.log(` Kid: ${result.issuer_signature.kid || '—'}`);
|
|
1609
|
+
console.log(` Valid: ${result.issuer_signature.valid ? '✓ YES' : '✗ NO'}`);
|
|
1610
|
+
console.log(` Key trusted: ${result.issuer_signature.key_trusted ? '✓ YES' : '✗ NO'}${result.issuer_signature.trust ? ` (${result.issuer_signature.trust})` : ''}`);
|
|
1611
|
+
if (!result.issuer_signature.valid && result.issuer_signature.reason) {
|
|
1612
|
+
console.log(` Reason: ${result.issuer_signature.reason}`);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
else {
|
|
1616
|
+
console.log(` Status: ${result.issuer_signature.reason || 'Not checked'}`);
|
|
1617
|
+
if (receipt.issuer_signature && !args.jwksFile && !args.fetchJwks && !args.jwksUrl) {
|
|
1618
|
+
console.log(` (pass --jwks-file, --fetch-jwks, or rely on the default trusted kid)`);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
console.log('');
|
|
1622
|
+
console.log(` Payer (on-chain)`);
|
|
1623
|
+
console.log(` ─────────────────────────────────────────────────`);
|
|
1624
|
+
if (result.payer.checked) {
|
|
1625
|
+
console.log(` Rail: ${result.payer.rail || '—'}`);
|
|
1626
|
+
console.log(` Payer: ${result.payer.payer_wallet || '—'}`);
|
|
1627
|
+
console.log(` Payee: ${result.payer.payee || '—'}`);
|
|
1628
|
+
console.log(` Asset: ${result.payer.asset || '—'}`);
|
|
1629
|
+
console.log(` Amount: ${formatAmount(result.payer.amount || null)}`);
|
|
1630
|
+
console.log(` Valid: ${result.payer.valid ? '✓ YES' : '✗ NO'}`);
|
|
1631
|
+
if (!result.payer.valid && result.payer.reason) {
|
|
1632
|
+
console.log(` Reason: ${result.payer.reason}`);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
else {
|
|
1636
|
+
console.log(` Status: ${result.payer.reason || 'Not checked'}`);
|
|
1637
|
+
if (result.issuer_signature.valid && result.payer.payer_wallet && result.tx && !args.checkPayer) {
|
|
1638
|
+
console.log(` (pass --check-payer to verify payer, payee, asset, and amount on-chain)`);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (result.claim_mismatches.length > 0) {
|
|
1642
|
+
console.log('');
|
|
1643
|
+
console.log(` Outer / signed mismatches`);
|
|
1644
|
+
console.log(` ─────────────────────────────────────────────────`);
|
|
1645
|
+
for (const mismatch of result.claim_mismatches) {
|
|
1646
|
+
console.log(` ${mismatch.field}: outer ${mismatch.outer} ≠ signed ${mismatch.signed}`);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
console.log('');
|
|
1650
|
+
printUnsigned(result.unsigned_fields);
|
|
1651
|
+
console.log('');
|
|
1652
|
+
console.log(` Nullifier`);
|
|
1653
|
+
console.log(` ─────────────────────────────────────────────────`);
|
|
1654
|
+
if (result.nullifier.nullifier) {
|
|
1655
|
+
console.log(` Value: ${result.nullifier.nullifier.slice(0, 18)}…`);
|
|
1656
|
+
if (args.checkNullifier) {
|
|
1657
|
+
console.log(` On-chain: ${result.nullifier.anchored ? '✓ ANCHORED' : '✗ NOT FOUND'}`);
|
|
1658
|
+
}
|
|
1659
|
+
else {
|
|
1660
|
+
console.log(` On-chain: (not checked — use --check-nullifier)`);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
else {
|
|
1664
|
+
console.log(` Status: No nullifier (Tier-1 receipt)`);
|
|
1665
|
+
}
|
|
1666
|
+
console.log('');
|
|
1667
|
+
printLane(result.receipt_lane);
|
|
1668
|
+
if (result.preimages.checked) {
|
|
1669
|
+
console.log(` Preimages: ${result.preimages.ok ? '✓ MATCH' : '✗ ' + result.preimages.errors.join('; ')}`);
|
|
1670
|
+
}
|
|
1671
|
+
if (result.issuer_history.checked) {
|
|
1672
|
+
console.log(` Issuer history: ${result.issuer_history.ok ? '✓ kid in window' : '✗ ' + (result.issuer_history.reason || 'failed')}`);
|
|
1673
|
+
}
|
|
1674
|
+
else if (result.issuer_history.warning) {
|
|
1675
|
+
console.log(` Issuer history: ${result.issuer_history.warning}`);
|
|
1676
|
+
}
|
|
1677
|
+
if (result.carry_forward?.status === 'VERIFIED_CARRIED_FORWARD') {
|
|
1678
|
+
const carried = result.carry_forward;
|
|
1679
|
+
console.log(` Log`);
|
|
1680
|
+
console.log(` ─────────────────────────────────────────────────`);
|
|
1681
|
+
console.log(` Status: ${carried.status}`);
|
|
1682
|
+
console.log(` Issued at: ${carried.issued_at}`);
|
|
1683
|
+
console.log(` Issued epoch: ${carried.issued_epoch}`);
|
|
1684
|
+
console.log(` Logged at: ${carried.logged_at}`);
|
|
1685
|
+
console.log(` Logged epoch: ${carried.logged_epoch}`);
|
|
1686
|
+
console.log(` Leaf index: ${carried.leaf_index}`);
|
|
1687
|
+
console.log('');
|
|
1688
|
+
}
|
|
1689
|
+
console.log(` Overall: ${(0, verdict_js_1.overallLine)(receiptVerdict, result.overall)}`);
|
|
1690
|
+
console.log(` ${receiptVerdict.message}`);
|
|
1691
|
+
if (receiptVerdict.history.source === 'bundled' && receiptVerdict.history.as_of) {
|
|
1692
|
+
console.log(` Issuer history: as of ${receiptVerdict.history.as_of}`);
|
|
1693
|
+
}
|
|
1694
|
+
if (receiptVerdict.next_step)
|
|
1695
|
+
console.log(` Next: ${receiptVerdict.next_step}`);
|
|
1696
|
+
if (result.errors.length > 0) {
|
|
1697
|
+
console.log(` Errors: ${result.errors.join(', ')}`);
|
|
1698
|
+
}
|
|
1699
|
+
console.log('');
|
|
1700
|
+
}
|
|
1701
|
+
return receiptVerdict.exit_code;
|
|
1702
|
+
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Node's fetch keeps its undici agent on this symbol after the first request.
|
|
1705
|
+
* There is no public handle for that pool. Closing it lets the loop drain.
|
|
1706
|
+
*/
|
|
1707
|
+
const UNDICI_GLOBAL_DISPATCHER = Symbol.for('undici.globalDispatcher.1');
|
|
1708
|
+
function fetchDispatcher() {
|
|
1709
|
+
const bag = globalThis;
|
|
1710
|
+
return bag[UNDICI_GLOBAL_DISPATCHER];
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* process.exit() while a fetch keep-alive socket is already CLOSING aborts
|
|
1714
|
+
* on Windows: Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)
|
|
1715
|
+
* (src/win/async.c). Set the code and close the pool so the loop can drain.
|
|
1716
|
+
*/
|
|
1717
|
+
async function finish(code) {
|
|
1718
|
+
process.exitCode = code;
|
|
1719
|
+
const dispatcher = fetchDispatcher();
|
|
1720
|
+
if (!dispatcher)
|
|
1721
|
+
return;
|
|
1722
|
+
try {
|
|
1723
|
+
const closing = dispatcher.close?.();
|
|
1724
|
+
if (closing)
|
|
1725
|
+
await closing;
|
|
1726
|
+
}
|
|
1727
|
+
catch {
|
|
1728
|
+
try {
|
|
1729
|
+
await dispatcher.destroy?.();
|
|
1730
|
+
}
|
|
1731
|
+
catch {
|
|
1732
|
+
// The verification result is already the exit code.
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
const RECEIPT_ID_RE = /^(chit|xfuel)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1737
|
+
function removedFlagMessage(argv) {
|
|
1738
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1739
|
+
const arg = argv[i];
|
|
1740
|
+
if (arg === '--')
|
|
1741
|
+
break;
|
|
1742
|
+
if (!arg.startsWith('-') || arg === '-')
|
|
1743
|
+
continue;
|
|
1744
|
+
if (REMOVED_040[arg])
|
|
1745
|
+
return REMOVED_040[arg];
|
|
1746
|
+
if (arg === '--issuer-history') {
|
|
1747
|
+
const value = argv[i + 1];
|
|
1748
|
+
if (!value || value.startsWith('-'))
|
|
1749
|
+
return 'removed in 0.4: --issuer-history needs a file';
|
|
1750
|
+
i += 1;
|
|
1751
|
+
continue;
|
|
1752
|
+
}
|
|
1753
|
+
if (arg === '--json' || arg === '--help' || arg === '-h' || arg === '--version' || arg === '-V')
|
|
1754
|
+
continue;
|
|
1755
|
+
const name = arg.includes('=') ? arg.slice(0, arg.indexOf('=')) : arg;
|
|
1756
|
+
return `removed in 0.4: ${name} is not a verifier flag`;
|
|
1757
|
+
}
|
|
1758
|
+
return null;
|
|
1759
|
+
}
|
|
1760
|
+
function parseLocked(argv) {
|
|
1761
|
+
const parsed = {
|
|
1762
|
+
json: false,
|
|
1763
|
+
help: false,
|
|
1764
|
+
version: false,
|
|
1765
|
+
issuerHistoryFile: null,
|
|
1766
|
+
positionals: [],
|
|
1767
|
+
};
|
|
1768
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1769
|
+
const arg = argv[i];
|
|
1770
|
+
if (arg === '--json')
|
|
1771
|
+
parsed.json = true;
|
|
1772
|
+
else if (arg === '--help' || arg === '-h')
|
|
1773
|
+
parsed.help = true;
|
|
1774
|
+
else if (arg === '--version' || arg === '-V')
|
|
1775
|
+
parsed.version = true;
|
|
1776
|
+
else if (arg === '--issuer-history' && argv[i + 1]) {
|
|
1777
|
+
parsed.issuerHistoryFile = argv[++i];
|
|
1778
|
+
}
|
|
1779
|
+
else if (!arg.startsWith('-') || arg === '-') {
|
|
1780
|
+
parsed.positionals.push(arg);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
return parsed;
|
|
1784
|
+
}
|
|
1785
|
+
function printLocked(verdict, json) {
|
|
1786
|
+
if (json) {
|
|
1787
|
+
console.log(JSON.stringify(verdict, null, 2));
|
|
1788
|
+
return verdict.exit_code;
|
|
1789
|
+
}
|
|
1790
|
+
for (const line of (0, verdict_js_1.humanVerdictLines)(verdict))
|
|
1791
|
+
console.log(line);
|
|
1792
|
+
return verdict.exit_code;
|
|
1793
|
+
}
|
|
1794
|
+
function lockedError(json, status, message, reason) {
|
|
1795
|
+
if (!json) {
|
|
1796
|
+
console.error(message);
|
|
1797
|
+
return status;
|
|
1798
|
+
}
|
|
1799
|
+
const failed = status === 1;
|
|
1800
|
+
const verdict = (0, verdict_js_1.buildVerifyVerdict)({
|
|
1801
|
+
version: packageVersion(),
|
|
1802
|
+
errors: failed ? [reason] : [],
|
|
1803
|
+
signatureChecked: failed,
|
|
1804
|
+
signatureValid: failed ? false : null,
|
|
1805
|
+
logInclusion: 'could_not_check',
|
|
1806
|
+
anchor: 'could_not_check',
|
|
1807
|
+
logReason: failed ? null : reason,
|
|
1808
|
+
});
|
|
1809
|
+
console.log(JSON.stringify({
|
|
1810
|
+
...verdict,
|
|
1811
|
+
result: failed ? 'invalid' : 'could_not_check',
|
|
1812
|
+
exit_code: status,
|
|
1813
|
+
reason,
|
|
1814
|
+
message,
|
|
1815
|
+
next_step: failed ? null : verdict.next_step,
|
|
1816
|
+
}, null, 2));
|
|
1817
|
+
return status;
|
|
1818
|
+
}
|
|
1819
|
+
function receiptJsonUrl(raw) {
|
|
1820
|
+
const url = new URL(raw);
|
|
1821
|
+
if (!url.pathname.endsWith('.json'))
|
|
1822
|
+
url.searchParams.set('format', 'json');
|
|
1823
|
+
return url.toString();
|
|
1824
|
+
}
|
|
1825
|
+
async function loadReceiptTarget(target) {
|
|
1826
|
+
if (target === '-') {
|
|
1827
|
+
return { receipt: JSON.parse((0, node_fs_1.readFileSync)(0, 'utf8')), origin: null };
|
|
1828
|
+
}
|
|
1829
|
+
let url = null;
|
|
1830
|
+
if (/^https?:\/\//i.test(target))
|
|
1831
|
+
url = receiptJsonUrl(target);
|
|
1832
|
+
else if (RECEIPT_ID_RE.test(target))
|
|
1833
|
+
url = `https://api.chit402.com/receipt/${target}?format=json`;
|
|
1834
|
+
if (url) {
|
|
1835
|
+
let res;
|
|
1836
|
+
try {
|
|
1837
|
+
res = await fetch(url, {
|
|
1838
|
+
headers: { accept: 'application/json' },
|
|
1839
|
+
signal: AbortSignal.timeout(LOG_FETCH_MS),
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
catch (err) {
|
|
1843
|
+
const wrapped = new Error(err instanceof Error ? err.message : String(err));
|
|
1844
|
+
wrapped.code = 'REMOTE';
|
|
1845
|
+
throw wrapped;
|
|
1846
|
+
}
|
|
1847
|
+
if (!res.ok) {
|
|
1848
|
+
const err = new Error(`fetch ${res.status}`);
|
|
1849
|
+
err.code = 'REMOTE';
|
|
1850
|
+
throw err;
|
|
1851
|
+
}
|
|
1852
|
+
const text = await res.text();
|
|
1853
|
+
try {
|
|
1854
|
+
return { receipt: JSON.parse(text), origin: new URL(url).origin };
|
|
1855
|
+
}
|
|
1856
|
+
catch {
|
|
1857
|
+
const err = new Error('receipt response was not JSON');
|
|
1858
|
+
err.code = 'REMOTE';
|
|
1859
|
+
throw err;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
return { receipt: JSON.parse((0, node_fs_1.readFileSync)(target, 'utf8')), origin: null };
|
|
1863
|
+
}
|
|
1864
|
+
function inputFailure(err, target) {
|
|
1865
|
+
const code = err && typeof err === 'object' && 'code' in err ? String(err.code) : '';
|
|
1866
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1867
|
+
const remote = code === 'REMOTE' || code === 'FETCH' || /fetch failed|fetch \d+|timeout|aborted|network|ENOTFOUND|ECONNREFUSED|EAI_AGAIN/i.test(message);
|
|
1868
|
+
if (remote) {
|
|
1869
|
+
return { status: 2, message: 'The receipt could not be fetched.', reason: 'receipt_unavailable' };
|
|
1870
|
+
}
|
|
1871
|
+
if (code === 'ENOENT' || message.includes('ENOENT')) {
|
|
1872
|
+
return { status: 1, message: `Error reading receipt: ${target}`, reason: 'unreadable' };
|
|
1873
|
+
}
|
|
1874
|
+
return { status: 1, message: `Error reading receipt: ${message}`, reason: 'unreadable' };
|
|
1875
|
+
}
|
|
1876
|
+
function shellInclusionAbsent(receipt) {
|
|
1877
|
+
const inclusion = receipt.inclusion;
|
|
1878
|
+
if (inclusion == null)
|
|
1879
|
+
return true;
|
|
1880
|
+
if (typeof inclusion !== 'object')
|
|
1881
|
+
return false;
|
|
1882
|
+
const row = inclusion;
|
|
1883
|
+
if (row.error === 'not_in_tree')
|
|
1884
|
+
return true;
|
|
1885
|
+
const leaf = row.leaf_hash ?? row.leaf ?? null;
|
|
1886
|
+
const head = row.signed_head;
|
|
1887
|
+
const headAbsent = head == null || (typeof head === 'object'
|
|
1888
|
+
&& head.root == null
|
|
1889
|
+
&& head.signature == null);
|
|
1890
|
+
return leaf == null && row.proof == null && headAbsent;
|
|
1891
|
+
}
|
|
1892
|
+
function shellLogReceipt(receipt, origin) {
|
|
1893
|
+
const task = receipt.task_id || receipt.receipt_id || null;
|
|
1894
|
+
if (!task || !RECEIPT_ID_RE.test(String(task)))
|
|
1895
|
+
return null;
|
|
1896
|
+
let host = origin;
|
|
1897
|
+
if (!host && typeof receipt.verify_url === 'string') {
|
|
1898
|
+
try {
|
|
1899
|
+
host = new URL(receipt.verify_url).origin;
|
|
1900
|
+
}
|
|
1901
|
+
catch {
|
|
1902
|
+
host = null;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
if (!host)
|
|
1906
|
+
host = 'https://api.chit402.com';
|
|
1907
|
+
const verifyUrl = `${host}/receipt/${encodeURIComponent(String(task))}`;
|
|
1908
|
+
if (!historyHostAllowed(verifyUrl))
|
|
1909
|
+
return null;
|
|
1910
|
+
return { verify_url: verifyUrl, task_id: String(task) };
|
|
1911
|
+
}
|
|
1912
|
+
async function main() {
|
|
1913
|
+
const argv = process.argv.slice(2);
|
|
1914
|
+
const jsonEarly = argv.includes('--json');
|
|
1915
|
+
const removed = removedFlagMessage(argv);
|
|
1916
|
+
if (removed)
|
|
1917
|
+
return lockedError(jsonEarly, 1, removed, 'removed_flag');
|
|
1918
|
+
const args = parseLocked(argv);
|
|
1919
|
+
if (args.version) {
|
|
1920
|
+
console.log(`chit402-verify ${packageVersion()}`);
|
|
1921
|
+
console.log((0, receipt_version_js_1.formatSupportedReceiptVersions)());
|
|
1922
|
+
return 0;
|
|
1923
|
+
}
|
|
1924
|
+
if (args.help) {
|
|
1925
|
+
console.log(HELP);
|
|
1926
|
+
return 0;
|
|
1927
|
+
}
|
|
1928
|
+
if (args.positionals.length === 0) {
|
|
1929
|
+
console.log(HELP);
|
|
1930
|
+
return 1;
|
|
1931
|
+
}
|
|
1932
|
+
if (args.positionals.length !== 1) {
|
|
1933
|
+
return lockedError(args.json, 1, 'removed in 0.4: pass one receipt id, url, or file', 'removed_flag');
|
|
1934
|
+
}
|
|
1935
|
+
let receipt;
|
|
1936
|
+
let receiptOrigin = null;
|
|
1937
|
+
try {
|
|
1938
|
+
const loaded = await loadReceiptTarget(args.positionals[0]);
|
|
1939
|
+
receipt = loaded.receipt;
|
|
1940
|
+
receiptOrigin = loaded.origin;
|
|
1941
|
+
}
|
|
1942
|
+
catch (err) {
|
|
1943
|
+
const failure = inputFailure(err, args.positionals[0]);
|
|
1944
|
+
if (failure.status === 2) {
|
|
1945
|
+
const verdict = (0, verdict_js_1.buildVerifyVerdict)({
|
|
1946
|
+
version: packageVersion(),
|
|
1947
|
+
absentReason: failure.reason,
|
|
1948
|
+
});
|
|
1949
|
+
return printLocked({
|
|
1950
|
+
...verdict,
|
|
1951
|
+
message: `${failure.message} It was NOT found invalid.`,
|
|
1952
|
+
next_step: 'Fetch the receipt again, then re-run.',
|
|
1953
|
+
}, args.json);
|
|
1954
|
+
}
|
|
1955
|
+
return lockedError(args.json, failure.status, failure.message, failure.reason);
|
|
1956
|
+
}
|
|
1957
|
+
const trustedKids = [...index_js_1.DEFAULT_TRUSTED_ISSUER_KIDS];
|
|
1958
|
+
let fileBytes = null;
|
|
1959
|
+
if (args.issuerHistoryFile) {
|
|
1960
|
+
try {
|
|
1961
|
+
fileBytes = (0, node_fs_1.readFileSync)(args.issuerHistoryFile, 'utf8');
|
|
1962
|
+
}
|
|
1963
|
+
catch (err) {
|
|
1964
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1965
|
+
return lockedError(args.json, 1, `Error reading issuer history: ${message}`, 'unreadable');
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
let resolved;
|
|
1969
|
+
try {
|
|
1970
|
+
resolved = await (0, issuer_history_js_1.resolveIssuerHistory)({
|
|
1971
|
+
fileBytes,
|
|
1972
|
+
receipt,
|
|
1973
|
+
trustedKids,
|
|
1974
|
+
timeoutMs: LOG_FETCH_MS,
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
catch (err) {
|
|
1978
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1979
|
+
return lockedError(args.json, 1, `Error reading issuer history: ${message}`, 'unreadable');
|
|
1980
|
+
}
|
|
1981
|
+
if ((0, index_js_1.isRefusalDocument)(receipt)) {
|
|
1982
|
+
const result = (0, index_js_1.verifyRefusal)(receipt, { trustedKids });
|
|
1983
|
+
const verdict = (0, verdict_js_1.buildVerifyVerdict)({
|
|
1984
|
+
version: packageVersion(),
|
|
1985
|
+
receiptId: result.refusal_id || null,
|
|
1986
|
+
displayId: result.refusal_id || null,
|
|
1987
|
+
payloadVersion: result.payload_version ?? null,
|
|
1988
|
+
historySource: resolved.source,
|
|
1989
|
+
historyHead: resolved.headHash,
|
|
1990
|
+
historyCount: resolved.entryCount,
|
|
1991
|
+
historyAsOf: resolved.asOf,
|
|
1992
|
+
signatureValid: result.valid,
|
|
1993
|
+
signatureChecked: true,
|
|
1994
|
+
historyChecked: !resolved.invalid && resolved.document != null,
|
|
1995
|
+
historyOk: !resolved.invalid && resolved.document != null,
|
|
1996
|
+
historyReason: resolved.reason,
|
|
1997
|
+
historyMissing: resolved.document == null && !resolved.invalid,
|
|
1998
|
+
errors: result.errors,
|
|
1999
|
+
logInclusion: 'unclaimed',
|
|
2000
|
+
anchor: 'unclaimed',
|
|
2001
|
+
});
|
|
2002
|
+
return printLocked(verdict, args.json);
|
|
2003
|
+
}
|
|
2004
|
+
const unsigned = typeof receipt.issuer_signature?.jws !== 'string' || receipt.issuer_signature.jws.length === 0;
|
|
2005
|
+
if (unsigned || (0, shell_js_1.isReceiptShell)(receipt)) {
|
|
2006
|
+
let logInclusion = 'unclaimed';
|
|
2007
|
+
let anchorState = 'unclaimed';
|
|
2008
|
+
let logReason = 'signed_receipt_unavailable';
|
|
2009
|
+
if (shellInclusionAbsent(receipt)) {
|
|
2010
|
+
const logReceipt = shellLogReceipt(receipt, receiptOrigin);
|
|
2011
|
+
if (logReceipt && await publicShellAbsence(logReceipt, resolved.document)) {
|
|
2012
|
+
logInclusion = 'fail';
|
|
2013
|
+
anchorState = 'pass';
|
|
2014
|
+
logReason = 'not_in_log';
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
const notInLog = logReason === 'not_in_log';
|
|
2018
|
+
const verdict = (0, verdict_js_1.buildVerifyVerdict)({
|
|
2019
|
+
version: packageVersion(),
|
|
2020
|
+
receiptId: receipt.task_id || null,
|
|
2021
|
+
displayId: displayIdOf(receipt),
|
|
2022
|
+
payloadVersion: null,
|
|
2023
|
+
historySource: resolved.source,
|
|
2024
|
+
historyHead: resolved.headHash,
|
|
2025
|
+
historyCount: resolved.entryCount,
|
|
2026
|
+
historyAsOf: resolved.asOf,
|
|
2027
|
+
signatureChecked: false,
|
|
2028
|
+
historyReason: notInLog ? null : 'signed_receipt_unavailable',
|
|
2029
|
+
historyMissing: false,
|
|
2030
|
+
errors: [],
|
|
2031
|
+
logInclusion,
|
|
2032
|
+
anchor: anchorState,
|
|
2033
|
+
logReason: notInLog ? 'not_in_log' : null,
|
|
2034
|
+
});
|
|
2035
|
+
return printLocked(verdict, args.json);
|
|
2036
|
+
}
|
|
2037
|
+
const result = await (0, index_js_1.verifyReceipt)(receipt, {
|
|
2038
|
+
trustedKids,
|
|
2039
|
+
requirePreimages: true,
|
|
2040
|
+
...historyOptions(resolved),
|
|
2041
|
+
});
|
|
2042
|
+
const unsupported = (result.errors || []).includes('unsupported_version');
|
|
2043
|
+
let logInclusion = 'unclaimed';
|
|
2044
|
+
let anchorState = 'unclaimed';
|
|
2045
|
+
let logReason = null;
|
|
2046
|
+
let log = null;
|
|
2047
|
+
const signatureOk = result.issuer_signature.valid === true && !resolved.invalid && !unsupported;
|
|
2048
|
+
const logHostOk = typeof receipt.verify_url === 'string' && historyHostAllowed(receipt.verify_url);
|
|
2049
|
+
if (signatureOk && result.issuer_history.ok && logHostOk) {
|
|
2050
|
+
const fresh = await freshPublicLog(receipt, resolved.document);
|
|
2051
|
+
logInclusion = fresh.logInclusion;
|
|
2052
|
+
anchorState = fresh.anchor;
|
|
2053
|
+
logReason = fresh.logReason;
|
|
2054
|
+
log = fresh.log;
|
|
2055
|
+
}
|
|
2056
|
+
const verdict = (0, verdict_js_1.buildVerifyVerdict)({
|
|
2057
|
+
version: packageVersion(),
|
|
2058
|
+
receiptId: result.receipt_id,
|
|
2059
|
+
displayId: displayIdOf(receipt),
|
|
2060
|
+
payloadVersion: result.receipt_version ?? null,
|
|
2061
|
+
historySource: resolved.source,
|
|
2062
|
+
historyHead: resolved.headHash,
|
|
2063
|
+
historyCount: resolved.entryCount,
|
|
2064
|
+
historyAsOf: resolved.asOf,
|
|
2065
|
+
signatureValid: unsupported ? null : result.issuer_signature.valid === true,
|
|
2066
|
+
signatureChecked: unsupported ? false : Boolean(result.issuer_signature.checked),
|
|
2067
|
+
historyChecked: unsupported ? false : (resolved.invalid || result.issuer_history.checked),
|
|
2068
|
+
historyOk: unsupported ? false : (resolved.invalid ? false : result.issuer_history.ok),
|
|
2069
|
+
historyReason: resolved.invalid ? (resolved.reason || result.issuer_history.reason) : result.issuer_history.reason,
|
|
2070
|
+
historyMissing: unsupported ? true : (resolved.document == null && !resolved.invalid),
|
|
2071
|
+
bindingExpected: !!result.binding.expected,
|
|
2072
|
+
bindingMatches: result.binding.matches,
|
|
2073
|
+
claimMismatches: result.claim_mismatches.length,
|
|
2074
|
+
payerChecked: result.payer.checked,
|
|
2075
|
+
payerValid: result.payer.valid,
|
|
2076
|
+
errors: result.errors,
|
|
2077
|
+
logInclusion,
|
|
2078
|
+
anchor: anchorState,
|
|
2079
|
+
logReason,
|
|
2080
|
+
log,
|
|
2081
|
+
});
|
|
2082
|
+
return printLocked(verdict, args.json);
|
|
2083
|
+
}
|
|
2084
|
+
main()
|
|
2085
|
+
.then((code) => finish(code))
|
|
2086
|
+
.catch((err) => {
|
|
2087
|
+
console.error('Unexpected error:', err);
|
|
2088
|
+
return finish(1);
|
|
2089
|
+
});
|
|
2090
|
+
//# sourceMappingURL=cli.js.map
|