37 lines
4.0 KiB
JavaScript
37 lines
4.0 KiB
JavaScript
#!/usr/bin/env node
|
|
// verifica-chitanta.mjs: a reader's check of a payment receipt (roadmap point 25), without ethers: the receipt file holds the
|
|
// instruction, the settlement envelope, the policy and the inputs the issuer judged; with --rpc the transaction and its receipt are
|
|
// read from the chain by plain JSON-RPC and compared; with --verify-proof the AIP-23 reference verifier judges the envelope and,
|
|
// with --rpc, its notarization under a post-quantum certified anchor (finality).
|
|
// node verifica-chitanta.mjs <receipt.json> [--rpc URL] [--verify-proof <verify-proof.mjs>] [--chain 28001]
|
|
// Exit 0 VALID, 1 INVALID, 2 could not measure (no chain answer, no verifier).
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { verificaChitanta } from './chitanta.mjs';
|
|
|
|
const a = process.argv.slice(2); const opt = (k, d = null) => { const i = a.indexOf(k); return i >= 0 && i + 1 < a.length ? a[i + 1] : d; };
|
|
const f = a[0]; if (!f || !fs.existsSync(f)) { console.log('usage: verifica-chitanta.mjs <receipt.json> [--rpc URL] [--verify-proof path] [--chain id]'); process.exit(2); }
|
|
const r = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
const rpc = opt('--rpc'); const vp = opt('--verify-proof', process.env.AERE_VERIFY_PROOF || null); const chain = opt('--chain', r.instruction && r.instruction.chainId);
|
|
let tx = null, receipt = null, nem = null;
|
|
if (rpc) {
|
|
const apel = async (method, params) => { const x = await fetch(rpc, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }), signal: AbortSignal.timeout(20000) }); const j = await x.json(); if (j.error) throw new Error(method + ': ' + j.error.message); return j.result; };
|
|
try { tx = await apel('eth_getTransactionByHash', [r.instruction.txHash]); receipt = await apel('eth_getTransactionReceipt', [r.instruction.txHash]); if (!tx || !receipt) nem = 'the chain does not know transaction ' + r.instruction.txHash; } catch (e) { nem = 'the chain did not answer: ' + e.message; }
|
|
}
|
|
const v = verificaChitanta({ instruction: r.instruction, envelope: r.envelope }, { policy: r.policy, inputs: r.inputs, tx, receipt });
|
|
let fin = null, verdictPlic = null;
|
|
if (vp && fs.existsSync(vp)) {
|
|
const t = path.join(os.tmpdir(), 'chitanta-' + process.pid + '.json'); fs.writeFileSync(t, JSON.stringify(r.envelope));
|
|
try { const p = spawnSync(process.execPath, [vp, t, '--json', ...(rpc ? ['--rpc', rpc, '--chain', String(chain)] : [])], { encoding: 'utf8', timeout: 300000 }); try { const j = JSON.parse(p.stdout); verdictPlic = j.verdict; fin = (j.levels || []).find((l) => l.level === 'finality'); } catch { nem = nem || 'the reference verifier gave no JSON'; } } finally { fs.rmSync(t, { force: true }); }
|
|
} else if (vp) nem = nem || 'the reference verifier is not at ' + vp;
|
|
const randuri = [`receipt: ${r.instruction.amountUsd} USD = ${r.instruction.amount} units of ${r.instruction.asset} at ${r.instruction.rate.usdPerUnit} USD/unit (${r.instruction.rate.source}), ${r.instruction.payer.slice(0, 10)}.. -> ${r.instruction.payee.slice(0, 10)}.., tx ${r.instruction.txHash.slice(0, 18)}.., risk ${r.instruction.risk.decision} under policy ${r.instruction.risk.policyHash.slice(0, 18)}..`,
|
|
`rules: ${v.verdict}${v.motive.length ? ': ' + v.motive.join('; ') : ''}${v.chainChecked ? ' (transaction compared on chain)' : ' (chain not consulted)'}`,
|
|
verdictPlic ? `envelope (reference verifier): ${verdictPlic}; finality ${fin ? fin.state + ': ' + String(fin.detail).slice(0, 140) : 'absent'}` : 'envelope (reference verifier): not run'];
|
|
console.log(randuri.join('\n'));
|
|
const invalid = v.verdict === 'INVALID' || verdictPlic === 'INVALID' || (fin && fin.state === 'FAILED');
|
|
process.exitCode = invalid ? 1 : nem ? 2 : 0;
|
|
if (nem && !invalid) console.log('UNMEASURED: ' + nem);
|
|
else console.log(invalid ? 'INVALID' : 'VALID' + (fin && fin.state === 'PASSED' ? ', notarized under a post-quantum certified anchor' : ''));
|