64 lines
7.2 KiB
JavaScript
64 lines
7.2 KiB
JavaScript
// The reference apps' checks against real Devnet 3 data: the genuine case verifies, every tampered case is refused.
|
|
// Known-failed first. Runs under Node 22 with the two noble libraries installed beside this file (npm install here):
|
|
// node verify.test.mjs [receipt.json] [balance.json] [checkpoint.json] default: ../fixtures/dn3-*.json
|
|
// Exits 1 on the first case that does not behave. The same core.js runs in the browser pages and in the one-file
|
|
// offline receipt verifier, so this is the test that proves their negative cases.
|
|
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, verifyBalance } from '../../../site/lc/core.js';
|
|
|
|
const deps = { blake2b, bls: bls12_381, keccak: keccak_256 };
|
|
const here = new URL('.', import.meta.url).pathname;
|
|
const load = p => JSON.parse(readFileSync(p, 'utf8'));
|
|
const clone = x => JSON.parse(JSON.stringify(x));
|
|
const flipHex = (s, at) => { const h = s.replace(/^0x/, ''); const i = Math.min(at, h.length - 1); const d = (parseInt(h[i], 16) ^ 1).toString(16); return (s.startsWith('0x') ? '0x' : '') + h.slice(0, i) + d + h.slice(i + 1); };
|
|
let failed = 0;
|
|
function expect(name, result, want) {
|
|
const ok = result.verified === want;
|
|
const line = `${ok ? 'ok ' : 'FAIL'} ${want ? 'verifies' : 'refused '} ${name}${result.verified ? '' : ' :: ' + result.reason}`;
|
|
console.log(line); if (!ok) failed++;
|
|
}
|
|
|
|
const receiptPath = process.argv[2] || here + '../fixtures/dn3-receipt.json';
|
|
const receipt = load(receiptPath);
|
|
console.log(`receipt ${receipt.tx_hash.slice(0, 12)} on ${receipt.chain_id}, checkpoint ${receipt.checkpoint.index}, ${receipt.headers.length} headers`);
|
|
// the known-failed cases first
|
|
{ const d = clone(receipt); d.raw_tx_hex = flipHex(d.raw_tx_hex, 40); expect('raw transaction altered by one nibble', verifyReceipt(d, deps), false); }
|
|
{ const d = clone(receipt); d.tx_hash = flipHex(d.tx_hash, 10); expect('transaction hash altered', verifyReceipt(d, deps), false); }
|
|
{ const d = clone(receipt); d.including_block.leaf_index = d.including_block.leaf_index === 1 ? 2 : 1; expect('merkle leaf index moved', verifyReceipt(d, deps), false); }
|
|
{ const d = clone(receipt); d.including_block.merkle_siblings[0] = flipHex(d.including_block.merkle_siblings[0], 3); expect('a merkle sibling altered', verifyReceipt(d, deps), false); }
|
|
{ const d = clone(receipt); if (d.headers.length > 2) { d.headers.splice(Math.floor(d.headers.length / 2), 1); expect('a header removed from the path', verifyReceipt(d, deps), false); } }
|
|
{ const d = clone(receipt); const h = d.headers[Math.floor(d.headers.length / 2)]; h.nonce = String(BigInt(h.nonce) ^ 1n); expect('a header nonce altered', verifyReceipt(d, deps), false); }
|
|
{ const d = clone(receipt); d.headers[0].hash_merkle_root = flipHex(d.headers[0].hash_merkle_root, 5); expect('the including block\'s body root altered', verifyReceipt(d, deps), false); }
|
|
{ const d = clone(receipt); const c = d.checkpoint.certificate; c.certificate.aggregate_signature_hex = flipHex(c.certificate.aggregate_signature_hex, 20); expect('certificate signature altered', verifyReceipt(d, deps), false); }
|
|
{ const d = clone(receipt); d.checkpoint.hash = flipHex(d.checkpoint.hash, 60); expect('receipt points at another checkpoint than its certificate', verifyReceipt(d, deps), false); }
|
|
{ const d = clone(receipt); const c = d.checkpoint.certificate; const bm = parseInt(c.certificate.bitmap_hex.slice(0, 2), 16); let p = 0; while (!(bm & (1 << p))) p++; c.certificate.bitmap_hex = (bm & ~(1 << p)).toString(16).padStart(2, '0') + c.certificate.bitmap_hex.slice(2); expect(`a signer dropped from the certificate bitmap (position ${p})`, verifyReceipt(d, deps), false); }
|
|
// then the genuine one
|
|
const r = verifyReceipt(receipt, deps);
|
|
expect(`genuine receipt (${r.ms} ms)`, r, true);
|
|
if (r.verified) console.log(` ${r.tx.type === 2 ? 'EIP-1559' : 'type ' + r.tx.type} tx to ${r.tx.to}, value ${r.tx.value} wei, in block ${r.block.slice(0, 12)} at DAA ${r.block_daa}, ${r.headers} headers to checkpoint ${r.checkpoint}`);
|
|
|
|
const balancePath = process.argv[3] || here + '../fixtures/dn3-balance.json';
|
|
let balance = null; try { balance = load(balancePath); } catch { console.log('no balance fixture yet (' + balancePath + '), skipping the balance cases'); }
|
|
if (balance) {
|
|
const cp = balance.checkpoint_certificate || load(process.argv[4] || here + '../fixtures/dn3-checkpoint.json'); // the certificate the service answered with
|
|
console.log(`balance of ${balance.address} at chain block ${balance.segment.last}, checkpoint ${balance.checkpoint.index}, ${balance.headers.length} headers`);
|
|
{ const d = clone(balance); d.account.accountProof[d.account.accountProof.length - 1] = flipHex(d.account.accountProof[d.account.accountProof.length - 1], 30); expect('account proof: a node altered', verifyBalance(cp, d, deps), false); }
|
|
{ const d = clone(balance); d.account.balance = '0x' + (BigInt(d.account.balance) + 1n).toString(16); expect('reported balance raised by one wei', verifyBalance(cp, d, deps), false); }
|
|
{ const d = clone(balance); d.account.stateRoot = flipHex(d.account.stateRoot, 8); expect('state root altered', verifyBalance(cp, d, deps), false); }
|
|
{ const d = clone(balance); d.segment_record_hex = flipHex(d.segment_record_hex, 2 * (2 + 8 + 8 + 32 + 48 + 20 + 148) + 3); expect('post_root altered inside the segment record', verifyBalance(cp, d, deps), false); }
|
|
{ const d = clone(balance); d.segment_record_hex = flipHex(d.segment_record_hex, d.segment_record_hex.length - 10); expect('aggregator signature altered', verifyBalance(cp, d, deps), false); }
|
|
{ const d = clone(balance); d.carrier.coinbase.payload = flipHex(d.carrier.coinbase.payload, 30); expect('coinbase payload altered', verifyBalance(cp, d, deps), false); }
|
|
{ const d = clone(balance); if (d.carrier.merkle_siblings.length) { d.carrier.merkle_siblings[0] = flipHex(d.carrier.merkle_siblings[0], 1); expect('a merkle sibling altered', verifyBalance(cp, d, deps), false); } else { d.carrier.merkle_siblings.push('00'.repeat(32)); expect('a merkle sibling added to a one-leaf body', verifyBalance(cp, d, deps), false); } }
|
|
{ const d = clone(balance); if (d.headers.length > 2) { d.headers.splice(1, 1); expect('a header removed from the path', verifyBalance(cp, d, deps), false); } }
|
|
{ const d = clone(balance); d.checkpoint.hash = flipHex(d.checkpoint.hash, 1); expect('proof names another checkpoint than the certificate', verifyBalance(cp, d, deps), false); }
|
|
{ const c = clone(cp); c.certificate.aggregate_signature_hex = flipHex(c.certificate.aggregate_signature_hex, 20); expect('certificate signature altered', verifyBalance(c, balance, deps), false); }
|
|
{ const d = clone(balance); d.address = '0x' + '11'.repeat(20); expect('another address with this proof', verifyBalance(cp, d, deps), false); }
|
|
const b = verifyBalance(cp, balance, deps);
|
|
expect(`genuine balance (${b.ms} ms)`, b, true);
|
|
if (b.verified) { console.log(` ${b.balance_wei} wei at chain block ${b.block}, ${b.headers} headers, ${b.aggregator}`); for (const s of b.steps) console.log(' ' + (s.ok ? 'ok ' : 'no ') + s.name + ': ' + s.detail); }
|
|
}
|
|
console.log(failed ? `FAILED ${failed}` : 'RESULT every case behaved');
|
|
process.exit(failed ? 1 : 0);
|