33 lines
2.7 KiB
JavaScript
33 lines
2.7 KiB
JavaScript
// Igneum payment receipt, the offline verifier (8 October 2026). This is the SOURCE; the one-file build at
|
|
// site/lc/verify-receipt.js bundles it with @noble/hashes (BLAKE2b, keccak) and @noble/curves (BLS12-381) so the
|
|
// shipped file needs no npm and no network: `node verify-receipt.js receipt.json [--tamper]`. Build: bundle.sh (esbuild
|
|
// on the build box). The checks are site/lc/core.js, the same file the browser page runs.
|
|
import { readFileSync } from 'node:fs';
|
|
import { blake2b } from '@noble/hashes/blake2.js';
|
|
import { keccak_256 } from '@noble/hashes/sha3.js';
|
|
import { bls12_381 } from '@noble/curves/bls12-381.js';
|
|
import { verifyReceipt, formatIgn } from '../../../site/lc/core.js';
|
|
|
|
const args = process.argv.slice(2);
|
|
const file = args.find(a => !a.startsWith('--'));
|
|
if (!file) { console.error('usage: node verify-receipt.js receipt.json [--tamper] (Igneum receipt, format igneum-receipt-v1; verifies offline)'); process.exit(2); }
|
|
const receipt = JSON.parse(readFileSync(file, 'utf8'));
|
|
if (receipt.format !== 'igneum-receipt-v1') { console.error(`REFUSED: not an igneum-receipt-v1 file (format ${receipt.format})`); process.exit(1); }
|
|
const deps = { blake2b, bls: bls12_381, keccak: keccak_256 };
|
|
const print = r => { for (const s of r.steps) console.log(` ${s.ok ? 'ok ' : 'REFUSED'} ${s.name}\n ${s.detail}`); };
|
|
|
|
if (args.includes('--tamper')) {
|
|
// the known-failed case first: the same file with one nibble of the raw transaction altered must be refused
|
|
const bad = JSON.parse(JSON.stringify(receipt));
|
|
const h = bad.raw_tx_hex; const i = 40; bad.raw_tx_hex = h.slice(0, i) + (parseInt(h[i], 16) ^ 1).toString(16) + h.slice(i + 1);
|
|
const t = verifyReceipt(bad, deps);
|
|
console.log(`tampered copy (one nibble of the raw transaction): ${t.verified ? 'NOT REFUSED, this verifier is broken' : 'REFUSED'}${t.reason ? ' :: ' + t.reason : ''}`);
|
|
if (t.verified) process.exit(1);
|
|
}
|
|
const r = verifyReceipt(receipt, deps);
|
|
console.log(`receipt 0x${receipt.tx_hash} on ${receipt.chain_id} (Devnet 3, no value)`);
|
|
print(r);
|
|
if (!r.verified) { console.log(`REFUSED: ${r.reason}`); process.exit(1); }
|
|
const t = r.tx;
|
|
console.log(`VERIFIED in ${r.ms} ms: ${formatIgn(t.value)} IGN to ${t.to || 'contract creation'} (${t.value} wei), in block ${r.block.slice(0, 16)} at DAA ${r.block_daa} (${new Date(Number(r.block_time)).toISOString()}), final under checkpoint ${r.checkpoint}; ${r.headers} headers checked; ${r.certificate}.`);
|
|
console.log('As reported by the node, not proven by this file: from ' + (receipt.tx_as_reported && receipt.tx_as_reported.from) + (receipt.execution ? `, execution status ${receipt.execution.status === '0x1' ? 'success' : 'failed'}` : '') + '. The voter table with weights came from the node (spec 10.1).');
|