// Read-only cross-check of the frozen evidence. No node or filesystem mutations.
const fs = require('fs');
const crypto = require('crypto');
const path = require('path');
const base = __dirname;
const t = JSON.parse(fs.readFileSync(path.join(base, 'timelock-results.json')));
const p = JSON.parse(fs.readFileSync(path.join(base, 'password-results.json')));
const sha = v => crypto.createHash('sha256').update(v).digest();
const dsha = v => sha(sha(v));
const hexHash = v => sha(v).toString('hex');
const rows = [];
const check = (name, result) => { rows.push({ name, passed: Boolean(result) }); };
const tc = name => t.cases.find(c => c.case === name);
const pc = name => p.cases.find(c => c.case === name);
const hash160 = v => crypto.createHash('ripemd160').update(sha(v)).digest('hex');
function parseTx(hex) {
  const b = Buffer.from(hex, 'hex'); let o = 4;
  const vi = () => { const x = b[o++]; if (x < 253) return x; if (x === 253) { const n = b.readUInt16LE(o); o += 2; return n; } throw Error('Large varint outside this test'); };
  const count = vi(); if (count !== 1) throw Error('Expected a single legacy input');
  const inputTxid = Buffer.from(b.subarray(o, o + 32)).reverse().toString('hex'); o += 32;
  const vout = b.readUInt32LE(o); o += 4;
  const scriptLengthOffset = o; const scriptLength = vi(); const scriptStart = o; o += scriptLength;
  const scriptEnd = o; const sequence = b.readUInt32LE(o); o += 4;
  const outputs = vi(); const outputScripts = [];
  for (let i = 0; i < outputs; i++) { o += 8; const n = vi(); outputScripts.push(b.subarray(o, o + n).toString('hex')); o += n; }
  const locktime = b.readUInt32LE(o); o += 4;
  if (o !== b.length) throw Error('Unexpected trailing data');
  return { b, scriptLengthOffset, scriptStart, scriptEnd, script: b.subarray(scriptStart, scriptEnd), sequence, locktime, inputTxid, vout, outputScripts, txid: Buffer.from(dsha(b)).reverse().toString('hex') };
}
function pushes(b) { const a = []; for (let o = 0; o < b.length;) { let n = b[o++]; if (n === 76) n = b[o++]; if (n > 75) throw Error('Unexpected opcode in unlocking script'); a.push(b.subarray(o, o + n)); o += n; } return a; }
const rawTxs = new Map();
for (const r of t.rawRPC) { const hs = r.method === 'testmempoolaccept' ? r.params[0] : r.method === 'generateblock' ? r.params[1] : []; for (const h of hs || []) if (typeof h === 'string' && h.length > 100 && /^[0-9a-f]+$/.test(h)) { const tx = parseTx(h); rawTxs.set(tx.txid, tx); } }
let ordinaryPubkey = null;
for (const name of ['ordinary-future-nonfinal', 'ordinary-all-final-bypass', 'ordinary-same-input-alternate-now']) {
  const c = tc(name); const tx = rawTxs.get(c.mempool.txid); const [sig, pub] = pushes(tx.script);
  ordinaryPubkey ??= pub.toString('hex');
  const funded = t.rawRPC.filter(r => r.method === 'getrawtransaction').map(r => JSON.parse(r.stdout)).find(v => v.txid === tx.inputTxid);
  const script = Buffer.from(funded.vout.find(v => v.n === tx.vout).scriptPubKey.hex, 'hex');
  check(name + ' same ordinary pubkey and funded condition', pub.toString('hex') === ordinaryPubkey && script.toString('hex') === '76a914' + hash160(pub) + '88ac');
  const preimage = Buffer.concat([tx.b.subarray(0, tx.scriptLengthOffset), Buffer.from([script.length]), script, tx.b.subarray(tx.scriptEnd), Buffer.from('01000000', 'hex')]);
  const der = Buffer.concat([Buffer.from('3056301006072a8648ce3d020106052b8104000a034200', 'hex'), crypto.ECDH.convertKey(pub, 'secp256k1', undefined, undefined, 'uncompressed')]);
  const pubKey = crypto.createPublicKey({ key: der, format: 'der', type: 'spki' });
  check(name + ' independent signature verification', sig.at(-1) === 1 && crypto.verify('sha256', sha(preimage), pubKey, sig.subarray(0, -1)));
}
const first = tc('cltv-zero-lock-cannot-bypass');
const redeem = Buffer.from(first.redeemScript, 'hex');
const ownerInfo = t.rawRPC.filter(r => r.method === 'getaddressinfo').map(r => JSON.parse(r.stdout)).find(r => first.redeemScript.includes(r.pubkey));
check('CLTV exact required pubkey appears in owner RPC', ownerInfo && redeem.subarray(-34, -1).toString('hex') === ownerInfo.pubkey);
check('CLTV script ends CHECKLOCKTIMEVERIFY DROP pubkey CHECKSIG', redeem.subarray(3, 6).toString('hex') === 'b17521' && redeem.at(-1) === 0xac);
const spki = Buffer.concat([Buffer.from('3056301006072a8648ce3d020106052b8104000a034200', 'hex'), crypto.ECDH.convertKey(ownerInfo.pubkey, 'secp256k1', 'hex', undefined, 'uncompressed')]);
const key = crypto.createPublicKey({ key: spki, format: 'der', type: 'spki' });
let sharedInput = null;
for (const name of ['cltv-zero-lock-cannot-bypass', 'cltv-correct-lock-but-premature', 'cltv-final-sequence-cannot-bypass', 'cltv-key-signed-ripe-block-accepted']) {
  const c = tc(name); const tx = rawTxs.get(c.mempool.txid); const [sig, rs] = pushes(tx.script);
  const input = `${tx.inputTxid}:${tx.vout}`; sharedInput ??= input;
  check(name + ' same funded input and exact redeem script', input === sharedInput && rs.equals(redeem));
  const preimage = Buffer.concat([tx.b.subarray(0, tx.scriptLengthOffset), Buffer.from([redeem.length]), redeem, tx.b.subarray(tx.scriptEnd), Buffer.from('01000000', 'hex')]);
  check(name + ' independent secp256k1 SIGHASH_ALL signature check', sig.at(-1) === 1 && crypto.verify('sha256', sha(preimage), key, sig.subarray(0, -1)));
  check(name + ' actual locktime and sequence', tx.locktime === (name.includes('zero-lock') ? 0 : c.requiredLockHeight) && tx.sequence === (name.includes('final-sequence') ? 4294967295 : 4294967294));
}
const redeemHash = crypto.createHash('ripemd160').update(sha(redeem)).digest('hex');
check('CLTV funded output commits the exact redeem script', t.rawRPC.some(r => r.method === 'getrawtransaction' && JSON.parse(r.stdout).vout.some(v => v.scriptPubKey.hex === 'a914' + redeemHash + '87')));
const chains = t.rawRPC.filter(r => r.method === 'getblockchaininfo').map(r => JSON.parse(r.stdout));
check('CLTV and CSV really active before CLTV cases; MWEB inactive', chains.some(x => x.blocks === 1351 && x.softforks.bip65.active && x.softforks.csv.active && !x.softforks.mweb.active));
const boundary = tc('ordinary-next-block-equals-lock'); const later = tc('ordinary-next-block-greater-than-lock');
check('Boundary compares one identical candidate at equality and T+1', boundary.mempool.txid === later.mempool.txid && !boundary.mempool.allowed && later.mempool.allowed && boundary.nextBlockHeight === boundary.lockTime && later.nextBlockHeight === later.lockTime + 1);
const early = tc('ordinary-alternate-confirmed-before-future-lock');
check('Earlier alternate confirmation before future lock', early.transactionIncluded && early.confirmedAtHeight < early.lockTime);
const cKey = pc('new-independent-coin-needs-independent-key');
const cHex = cKey.independentWalletSigning.hex;
check('Independent C is signed and admitted, not actually sent', cKey.independentWalletSigning.complete && cKey.independentWalletMempool.allowed && !p.rawRPC.some(r => r.method === 'sendrawtransaction' && r.params[0] === cHex));
const old = pc('old-key-still-signs-spent-outpoint');
check('Old key signing vs spent input uses distinct candidate', old.signingComplete && old.mempool['reject-reason'] === 'missing-inputs' && old.mempool.txid !== pc('confirmed-transfer-spends-old-outpoint').oldTransactionRetest.txid);
function base58(s) { let n = 0n; for (const c of s) { const i = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'.indexOf(c); if (i < 0) return null; n = n * 58n + BigInt(i); } let h = n.toString(16); if (h.length % 2) h = '0' + h; return Buffer.concat([Buffer.alloc((s.match(/^1*/) || [''])[0].length), Buffer.from(h, 'hex')]); }
function isWIF(s) { const b = base58(s); if (!b || ![37, 38].includes(b.length) || ![0x80, 0xef].includes(b[0])) return false; return dsha(b.subarray(0, -4)).subarray(0, 4).equals(b.subarray(-4)); }
for (const [name, j] of [['timelock', t], ['password', p]]) {
  const serial = JSON.stringify(j);
  const candidates = [...serial.matchAll(/(?:^|[^A-Za-z0-9])([5KLc9][1-9A-HJ-NP-Za-km-z]{50,51})(?![A-Za-z0-9])/g)].map(m => m[1]);
  check(name + ' no valid WIF or extended private key', !candidates.some(isWIF) && !/(?:xprv|tprv)[1-9A-HJ-NP-Za-km-z]{90,}/.test(serial));
  check(name + ' no native filesystem or server credentials in results', !/C:[\\/]|[\\/](?:Users|home|tmp|var[\\/]www)[\\/]|__cookie__|rpcpassword|Authorization:\s*Basic/.test(serial));
  check(name + ' wallet passphrase method arguments are redacted', j.rawRPC.filter(r => /^(walletpassphrase|walletpassphrasechange|encryptwallet)$/.test(r.method)).every(r => r.params.every(v => typeof v !== 'string' || v === '<disposable-passphrase>')));
  check(name + ' exported private-key trace is redacted', j.rawRPC.filter(r => r.method === 'dumpprivkey').every(r => r.stdout === '<disposable-private-key-redacted>' && r.params[0] === '<disposable-regtest-address>'));
  check(name + ' recorded runner SHA matches frozen runner bytes', j.metadata.runnerSHA256 === hexHash(fs.readFileSync(path.join(base, 'core-controls.py.txt'))));
}
process.stdout.write(JSON.stringify({ checks: rows.length, allPassed: rows.every(x => x.passed), details: rows }, null, 2) + '\n');
if (rows.some(x => !x.passed)) process.exitCode = 1;
