#!/usr/bin/env node // verify-proof.mjs - reference verifier of the AERE Proof Protocol (AIP-23). // // A proof is an envelope { v, kind, statement, statementHash, signature?, notarization? }. This tool checks it without trusting the // producer and, when asked to check finality, without trusting the RPC endpoint either: // // 1 form the file is JSON, the envelope is an object with a statement (an object, not a list) and a statementHash // (0x + 64 hex). Whatever else the file holds (null, a number, a string, a list, text that is not JSON) is // refused HERE, by name: this tool is run by strangers on files it has never seen, and a file it cannot read as // an envelope is a refusal, never an exception // 2 integrity sha256(JSON.stringify(statement)) == statementHash // 3 signature present ? an ml-dsa-{44,65,87} signature over the canonical statement text, checked with node:crypto // (OpenSSL 3.5). The public key must BE a key of the scheme the signature declares (2026-10-03: until then a // signature declared ml-dsa-65 and made with an Ed25519, RSA or P-256 key was reported PASSED as ml-dsa-65); a // signature field that is not {scheme, publicKey, signature}, or whose key cannot be parsed, FAILS; a scheme // this tool cannot check is reported UNMEASURED, never passed : absent // 4 finality (--rpc given, or the envelope claims a notarization) the chain's most recent anchor is verified by // verify-anchor.mjs (beside this file: every post-quantum seal of its certificate checked against the published // manifests); the header of the anchor's parent is re-encoded and must hash to the parent hash those seals signed; // that header's state root then anchors a Merkle proof (eth_getProof) that the AereNotary contract (its code hash // pinned below) holds this statementHash, and the time it was first seen. The endpoint is only a courier: a header, // a proof or a value it altered would not match the certified hash. A courier of another chain (its eth_chainId is // not the chain checked) or one that does not say which chain it serves is UNMEASURED, by name (since 2026-10-27; // until then it made the anchor verifier accuse a healthy chain and this level FAILED) // 5 witness (2026-10-18, the Proof line: a Bitcoin witness for every notarization) present when the envelope carries // `bitcoinWitness` (written by tools/aere-quantum-shield/martor-notar-dovada.mjs), or when the file IS such a witness // (format aere-notar-martor-1): the leaf (this statementHash as the chain recorded its notarization) hashes as recorded, // its Merkle inclusion proof leads to the lot's root, the .ots bytes are an OpenTimestamps proof FOR that root, and the // proof's restricted evaluation leads to the Merkle root of a Bitcoin block. Offline the level is UNMEASURED // (ots-header-not-chain-checked: nothing dates anything without a header); PASSED needs --btc-antet <80-byte header of // that block> or --live (two public explorers that must agree). What PASSED proves: the notarization existed no later // than that Bitcoin block's time. The judge is martor-notar-verifica.mjs, looked for in AERE_MARTOR_VERIFICA, beside // this file, then ../tools/aere-quantum-shield/; when it is not here the level is UNMEASURED and names `ots verify`. // // Usage: node verify-proof.mjs [--rpc ] [--chain 2800|28001] [--notary
] [--manifests ] // [--live] [--btc-antet <80-byte hex>] [--offline] [--json] // Exit: 0 every level present passed; 1 a level present failed; 2 a level present could not be measured here (or is pending). // --offline (2026-10-18): nothing leaves this machine. Form, integrity and signature are judged as always; a notarization the // envelope claims is reported UNMEASURED by name (never dropped, never passed); the Bitcoin witness is judged as far as its local // judge can without a header. This is the mode the browser page runs (deploy/website/verify.html bundles judge() below). // // The notary stores the block TIME of the first sighting (block.timestamp), not a block number. The block that holds the first // sighting is located through the endpoint's logs and reported as such (it is not part of what is certified). import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import crypto from 'node:crypto'; import { spawn } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import { keccak_256 } from '@noble/hashes/sha3'; import { pathToFileURL } from 'node:url'; import { rlpDecode, mptGet } from './mpt.mjs'; const HERE = path.dirname(fileURLToPath(import.meta.url)); export const NOTARY = { 2800: '0x4aB392c4Aca7D9D4C16c0b60a9514c5025bd58c7', 28001: '0x70099E62735500AA2F85B60C518551a57B202d54' }; export const RPC_DEFAULT = { 2800: 'https://rpc.aere.network', 28001: 'https://testnet-rpc.aere.network' }; export const MANIFESTS_DEFAULT = { 2800: 'https://aere.network/pq/manifests', 28001: 'https://testnet-rpc.aere.network/pq/manifests' }; // keccak256 of the AereNotary runtime code (the same bytes on 2800 and 28001): an address whose account proves another code hash // is not the notary, whatever the endpoint says const NOTARY_CODEHASH_FULL = '8eaa924081e9718fcee05ae64fadaa1815437ebbbcef5b87317c5879050452ce'; class Stop extends Error {} process.on('uncaughtException', (e) => { if (e instanceof Stop) return; console.error(e); process.exit(2); }); const stop = (code) => { process.exitCode = code; throw new Stop(); }; // ---------- minimal RLP and hashing ---------- const hexb = (h) => Buffer.from(String(h).replace(/^0x/, '').replace(/^(.(..)*)$/, '0$1'), 'hex'); const keccak = (b) => Buffer.from(keccak_256(b)); const encLen = (n, off) => { if (n < 56) return Buffer.from([off + n]); const l = Buffer.from(n.toString(16).replace(/^(.(..)*)$/, '0$1'), 'hex'); return Buffer.concat([Buffer.from([off + 55 + l.length]), l]); }; export function rlpEncode(x) { if (Array.isArray(x)) { const body = Buffer.concat(x.map(rlpEncode)); return Buffer.concat([encLen(body.length, 0xc0), body]); } const b = Buffer.from(x); if (b.length === 1 && b[0] < 0x80) return b; return Buffer.concat([encLen(b.length, 0x80), b]); } const scalar = (h) => { const n = BigInt(h); return n === 0n ? Buffer.alloc(0) : hexb(n.toString(16)); }; /** the hash the chain gives a header: QBFT strips the committed seals and zeroes the round before hashing (aere-node SPEC.md 2.3) */ export function onchainHash(b) { const e = rlpDecode(hexb(b.extraData)); const extra = rlpEncode([e[0], e[1], e[2], Buffer.alloc(0), []]); const f = [hexb(b.parentHash), hexb(b.sha3Uncles), hexb(b.miner), hexb(b.stateRoot), hexb(b.transactionsRoot), hexb(b.receiptsRoot), hexb(b.logsBloom), scalar(b.difficulty), scalar(b.number), scalar(b.gasLimit), scalar(b.gasUsed), scalar(b.timestamp), extra, hexb(b.mixHash), hexb(b.nonce)]; if (b.baseFeePerGas != null) f.push(scalar(b.baseFeePerGas)); if (b.withdrawalsRoot != null) f.push(hexb(b.withdrawalsRoot)); if (b.blobGasUsed != null) { f.push(scalar(b.blobGasUsed)); f.push(scalar(b.excessBlobGas)); } if (b.parentBeaconBlockRoot != null) f.push(hexb(b.parentBeaconBlockRoot)); if (b.requestsHash != null) f.push(hexb(b.requestsHash)); if (b.balHash != null) f.push(hexb(b.balHash)); return '0x' + keccak(rlpEncode(f)).toString('hex'); } // rlpDecode and mptGet live in mpt.mjs since 2026-10-02 (shared with verify-anchor.mjs, which proves the anchor contract's slot 0 // under the certified parent state, AIP-24 C4 (b)); re-exported here so that nothing that imported them from this file changes. export { rlpDecode, mptGet }; const level = (name, state, detail, extra) => ({ level: name, state, detail, ...(extra || {}) }); // ---------- levels 1 and 2 ---------- export function local(env, notJson = null) { const out = []; const obj = (x) => !!x && typeof x === 'object' && !Array.isArray(x); const formOk = obj(env) && obj(env.statement) && typeof env.statementHash === 'string' && /^0x[0-9a-fA-F]{64}$/.test(env.statementHash); const ce = (x) => (x === null ? 'null' : Array.isArray(x) ? 'a list' : typeof x === 'object' ? 'an object' : 'a ' + typeof x); // read through values that exist whatever the file holds: the form check is not the only thing between such a file and an exception const st = obj(env) ? env.statement : undefined, sh = obj(env) && typeof env.statementHash === 'string' ? env.statementHash : ''; out.push(level('form', formOk ? 'PASSED' : 'FAILED', formOk ? `kind=${String((obj(env) && env.kind) || (obj(st) && st.kind) || '?').slice(0, 80)}` : notJson ? notJson : !obj(env) ? `the file holds ${ce(env)}, not an envelope {statement, statementHash}` : 'the envelope lacks {statement (an object), statementHash 0x+64 hex}')); if (!formOk) return { out, text: null }; const text = String(JSON.stringify(st)); const h = '0x' + crypto.createHash('sha256').update(Buffer.from(text, 'utf8')).digest('hex'); const ok = h === sh.toLowerCase(); out.push(level('integrity', ok ? 'PASSED' : 'FAILED', ok ? `sha256(statement) == statementHash (${h.slice(0, 18)}..)` : `sha256(statement) is ${h.slice(0, 18)}.., not the statementHash ${sh.slice(0, 18)}..`)); return { out, text: ok ? text : null }; } // ---------- level 3 ---------- /** does this Node.js verify ML-DSA at all (OpenSSL 3.5)? asked once, by making a key: the answer is the runtime's, not a version string's */ let mlDsaHere = null; const canMlDsa = () => { if (mlDsaHere === null) { try { crypto.generateKeyPairSync('ml-dsa-44'); mlDsaHere = true; } catch { mlDsaHere = false; } } return mlDsaHere; }; export function signature(env, text) { if (env.signature === undefined || env.signature === null) return level('signature', 'ABSENT', 'the envelope carries no signature'); const s = env.signature; if (typeof s !== 'object' || Array.isArray(s)) return level('signature', 'FAILED', 'the signature field is not an object {scheme, publicKey, signature}'); const scheme = String(typeof s.scheme === 'string' ? s.scheme : typeof s.alg === 'string' ? s.alg : '').toLowerCase(); if (!['ml-dsa-44', 'ml-dsa-65', 'ml-dsa-87'].includes(scheme)) return level('signature', 'UNMEASURED', `a "${scheme.slice(0, 40) || '?'}" signature is not checkable by this reference verifier (the hybrid form is checked by @aere/pq-sign)`); if (typeof s.publicKey !== 'string' || !s.publicKey || typeof s.signature !== 'string' || !s.signature) return level('signature', 'FAILED', `${scheme}: the signature lacks its publicKey or its signature (two strings)`); if (!canMlDsa()) return level('signature', 'UNMEASURED', `${scheme} present, but this Node.js cannot check it (OpenSSL ${process.versions.openssl}); it needs OpenSSL 3.5`); let pub; try { pub = s.publicKey.includes('BEGIN') ? crypto.createPublicKey(s.publicKey) : crypto.createPublicKey({ key: Buffer.from(s.publicKey, 'base64'), format: 'der', type: 'spki' }); } catch (e) { return level('signature', 'FAILED', `${scheme}: the public key cannot be parsed (${String(e.message).split('\n')[0].slice(0, 60)})`); } // the key must be a key of the scheme declared: crypto.verify(null, ...) verifies with whatever key it is handed, so without this a // classical signature (Ed25519, RSA, P-256) declared as ml-dsa-65 was reported as a post-quantum signature that verifies const sameScheme = pub.asymmetricKeyType === scheme; if (!sameScheme) return level('signature', 'FAILED', `${scheme} declared, but the public key is ${String(pub.asymmetricKeyType)}: a signature is checked under the scheme it declares, with a key of that scheme`); let ok = false; try { const sig = /^[0-9a-fA-F]+$/.test(s.signature) ? Buffer.from(s.signature, 'hex') : Buffer.from(s.signature, 'base64'); ok = crypto.verify(null, Buffer.from(text, 'utf8'), pub, sig); } catch { ok = false; } return level('signature', ok ? 'PASSED' : 'FAILED', `${scheme}: ${ok ? 'verifies' : 'does not verify'} over the canonical statement text`); } // ---------- level 4 ---------- export async function finality(env, o) { if (!o.rpcGiven && !env.notarization) return level('finality', 'ABSENT', 'no notarization claimed and no --rpc given'); // offline (2026-10-18): a claim is a claim; it is reported as not measured here, by name, never dropped and never passed if (o.offline) return level('finality', 'UNMEASURED', `offline: the notarization this envelope claims (chain ${(env.notarization && env.notarization.chainId) || o.chain}) was not checked; this level needs the chain: run the reference verifier with the network (node verify-proof.mjs )`); const rpc = async (method, params) => { const r = await fetch(o.rpc, { method: 'POST', headers: { 'content-type': 'application/json', 'user-agent': 'aere-verify-proof/1' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) }); const j = await r.json(); if (j.error) throw new Error(method + ': ' + (j.error.message || JSON.stringify(j.error))); return j.result; }; // (0) 2026-10-27 (R9-S2): which chain the endpoint serves, read before anything else. A courier of ANOTHER chain says nothing about // chain o.chain: the level is UNMEASURED, named, never FAILED. Until this date the question was not asked, verify-anchor.mjs measured // the other chain's grid and reported a healthy chain as "not anchoring on a regular grid", and this level FAILED: the envelope // INVALID, on an endpoint that had only been given for the wrong chain. let served; try { served = Number(BigInt(await rpc('eth_chainId', []))); } catch (e) { return level('finality', 'UNMEASURED', `the endpoint ${o.rpc} did not say which chain it serves (eth_chainId: ${String(e && e.message).slice(0, 100)}); nothing about chain ${o.chain} was measured`); } if (served !== o.chain) return level('finality', 'UNMEASURED', `the endpoint ${o.rpc} serves chain ${served}, not chain ${o.chain}: nothing about chain ${o.chain} was measured there (give --rpc an endpoint of chain ${o.chain})`); // (a) the chain's most recent anchor, verified by verify-anchor.mjs, which reports the parent hash its seals signed // the report lives in a temporary directory that is removed on every path once it is read (2026-10-21: until then each online // finality check left one aere-proof-* directory behind; 296 of them after one stranger's pass) const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-proof-')); const report = path.join(tmpDir, 'anchor.json'); let va, rep = null; try { va = await new Promise((ok) => { const c = spawn(process.execPath, [path.join(HERE, 'verify-anchor.mjs')], { cwd: HERE, env: { ...process.env, RPC: o.rpc, CHAIN_ID: String(o.chain), MANIFESTS: o.manifests, ANCHORS: '1', PROPOSER_HEADERS: '2', ANCHOR_REPORT: report } }); let out = ''; c.stdout.on('data', (d) => { out += d; }); c.stderr.on('data', (d) => { out += d; }); c.on('error', (e) => ok({ code: null, out: String(e.message) })); c.on('close', (code) => ok({ code, out })); }); try { rep = JSON.parse(fs.readFileSync(report, 'utf8')); } catch { rep = null; } } finally { fs.rmSync(tmpDir, { recursive: true, force: true }); } // the last two MESSAGES of verify-anchor.mjs: a line that starts with blanks continues the one before it (its FAIL lines wrap), so it is // joined to it with a space, and only distinct messages are separated by ' | ' (2026-10-27, R9-S2: until then a wrapped sentence was // cut in the middle, "which is not a | multiple of 32") const mesaje = []; for (const l of va.out.replace(/\r/g, '').split('\n')) { if (!l.trim()) continue; if (/^\s/.test(l) && mesaje.length) mesaje[mesaje.length - 1] += ' ' + l.trim(); else mesaje.push(l.trim()); } const tail = mesaje.slice(-2).join(' | ').slice(0, 220); // exit 1 is a verdict only with verify-anchor's own verdict line: Node also exits 1 when the tool cannot start (a dependency not // installed: ERR_MODULE_NOT_FOUND), and that is not a failure of the chain (2026-09-29) const verdictLine = /^(FAIL:|PROBLEMS:)/m.test(va.out); if (va.code === 1 && verdictLine) return level('finality', 'FAILED', 'the chain\'s most recent anchor did not verify: ' + tail); if (va.code === 1) return level('finality', 'UNMEASURED', 'verify-anchor.mjs stopped without a verdict (is `npm install` done beside it?): ' + tail); if (va.code !== 0 || !rep || !rep.parentHash || rep.chainId !== o.chain) return level('finality', 'UNMEASURED', 'the anchor could not be verified here (verify-anchor.mjs exit ' + va.code + '): ' + tail); // (b) the parent header, bound to the certified hash by re-encoding const X = rep.parentNumber; let hdr; try { hdr = await rpc('eth_getBlockByNumber', ['0x' + X.toString(16), false]); } catch (e) { return level('finality', 'UNMEASURED', 'block ' + X + ' could not be read: ' + e.message); } if (!hdr) return level('finality', 'UNMEASURED', 'the endpoint returned no header for block ' + X); if (onchainHash(hdr) !== rep.parentHash) return level('finality', 'FAILED', `the header the endpoint served for block ${X} does not hash to ${rep.parentHash.slice(0, 18)}.., the hash anchor ${rep.anchor}'s certificate signed`); // (c) the notary's storage at that header's state root: mapping(bytes32 => uint64) firstSeen at slot 0 const slot = '0x' + keccak(Buffer.concat([hexb(env.statementHash), Buffer.alloc(32)])).toString('hex'); let pr; try { pr = await rpc('eth_getProof', [o.notary, [slot], '0x' + X.toString(16)]); } catch (e) { return level('finality', 'UNMEASURED', 'eth_getProof at block ' + X + ' failed (the endpoint may not keep that state): ' + e.message); } let acct, val; try { const a = mptGet(hdr.stateRoot, keccak(hexb(o.notary)), pr.accountProof || []); if (!a) return level('finality', 'FAILED', `the state of block ${X} has no account at ${o.notary}`); acct = rlpDecode(a); if (keccak(Buffer.alloc(0)).equals(acct[3]) || acct[3].toString('hex') !== NOTARY_CODEHASH_FULL) return level('finality', 'FAILED', `the account at ${o.notary} in the state of block ${X} is not the AereNotary contract (code hash 0x${acct[3].toString('hex').slice(0, 16)}..)`); const s = (pr.storageProof || [])[0]; if (!s) throw new Error('no storage proof returned'); const v = mptGet('0x' + acct[2].toString('hex'), keccak(hexb(slot)), s.proof || []); val = v ? BigInt('0x' + (rlpDecode(v).toString('hex') || '0')) : 0n; } catch (e) { return level('finality', 'FAILED', 'the Merkle proof for block ' + X + ' does not hold: ' + e.message); } const certified = `the state of block ${X}, the parent of anchor ${rep.anchor} whose post-quantum certificate verified here`; if (val === 0n) { let later = 0n; try { later = BigInt(await rpc('eth_call', [{ to: o.notary, data: '0x' + keccak(Buffer.from('proofOf(bytes32)')).subarray(0, 4).toString('hex') + env.statementHash.slice(2) }, 'latest'])); } catch { later = 0n; } if (later > 0n) return level('finality', 'PENDING', `notarized at ${new Date(Number(later) * 1000).toISOString()} (endpoint), after ${certified}; the next anchor will cover it`); return level('finality', env.notarization ? 'FAILED' : 'ABSENT', `not notarized: ${certified} holds no entry for this statementHash`); } const t = Number(val); let where = ''; try { const topic = '0x' + keccak(Buffer.from('Notarized(bytes32,address,uint64,bool)')).toString('hex'); let lo = 0, hi = X; while (lo < hi) { const mid = Math.floor((lo + hi) / 2); const b = await rpc('eth_getBlockByNumber', ['0x' + mid.toString(16), false]); if (Number(BigInt(b.timestamp)) >= t) hi = mid; else lo = mid + 1; } const logs = await rpc('eth_getLogs', [{ fromBlock: '0x' + Math.max(0, lo - 8).toString(16), toBlock: '0x' + Math.min(X, lo + 8).toString(16), address: o.notary, topics: [topic, env.statementHash] }]); const first = (logs || []).find((l) => /1$/.test(l.data)) || (logs || [])[0]; if (first) where = `; first seen in block ${Number(BigInt(first.blockNumber))}, transaction ${first.transactionHash.slice(0, 18)}.. (located through the endpoint's logs, not part of what is certified)`; } catch { where = ''; } // `certified`: the same reading as data (what the certified state holds, not what the envelope claims): a caller that needs the // notarization time takes it from here return level('finality', 'PASSED', `post-quantum: ${certified} records this statementHash in AereNotary ${o.notary} as first seen at ${new Date(t * 1000).toISOString()} (${t})${where}`, { certified: { chainId: o.chain, notary: o.notary, firstSeen: t, stateOfBlock: X, anchor: rep.anchor } }); } // ---------- main ---------- // ---------- level 5: the Bitcoin witness (martor-notar-verifica.mjs, one judge, never a second copy of it here) ---------- export const FORMAT_MARTOR = 'aere-notar-martor-1'; async function modulMartor() { const candidati = [process.env.AERE_MARTOR_VERIFICA, path.join(HERE, 'martor-notar-verifica.mjs'), path.join(HERE, '..', 'tools', 'aere-quantum-shield', 'martor-notar-verifica.mjs')].filter(Boolean); for (const c of candidati) { if (!fs.existsSync(c)) continue; try { const m = await import(pathToFileURL(c).href); if (typeof m.verificaMartor === 'function') return { m, cale: c }; } catch (e) { return { eroare: `${c}: ${String(e.message).slice(0, 120)}` }; } } return { lipsa: candidati }; } export async function witness(w, statementHash, o) { const mod = await modulMartor(); if (mod.eroare) return level('witness', 'UNMEASURED', 'the Bitcoin witness judge does not load here: ' + mod.eroare); if (!mod.m) return level('witness', 'UNMEASURED', `the Bitcoin witness judge (martor-notar-verifica.mjs) is not beside this verifier (looked in AERE_MARTOR_VERIFICA, ${HERE}, ../tools/aere-quantum-shield); the lot root can still be checked with the OpenTimestamps client: ots verify -d (martor-notar-dovada.mjs --ots-separat writes it)`); let r; try { r = await mod.m.verificaMartor(w, { statementHash, live: !!o.live, btcAntet: o.btcAntet || null }); } catch (e) { return level('witness', 'UNMEASURED', 'the Bitcoin witness judge stopped: ' + String(e && e.message).slice(0, 160)); } return level('witness', r.state, (r.reason ? r.reason + ': ' : '') + r.detail, { reason: r.reason || null, bitcoin: r.bitcoin || null }); } // ---------- the judgement, one function ---------- // 2026-10-18 (B12, the Proof line): the whole judgement moved out of main() into judge(), exported, so that the browser page // (deploy/website/verify.html, built by construieste-verificatorul-browser.mjs) runs THIS code, bundled, and not a second copy of // it that could diverge. main() is the command line around it and prints exactly what it printed before. /** a standalone witness file (format aere-notar-martor-1) is not an envelope: it is judged on its own level, and, since it names a * notarization, on finality for the hash it witnesses (the chain's certified state must hold that hash, or the witness covers nothing) */ export const witnessFile = (env) => !!(env && typeof env === 'object' && !Array.isArray(env) && env.format === FORMAT_MARTOR && env.leaf && typeof env.leaf === 'object' && typeof env.leaf.hash === 'string'); /** the options of one judgement. The chain: opt.chain if given; otherwise the chain the envelope's notarization declares, when it is a * known chain; otherwise 2800. (Before 2026-09-27 an envelope notarized on 28001, checked without --chain, was looked up on 2800 and * reported INVALID.) The notary stays the known contract of that chain (or opt.notary): an address written in the envelope is never * trusted as the notary. `offline`: nothing leaves this machine; the levels that need the chain are reported UNMEASURED by name. */ export function options(env, opt = {}) { const declared = env && typeof env === 'object' && (witnessFile(env) ? Number(env.chainId) : env.notarization && Number(env.notarization.chainId)); const chain = Number(opt.chain || (declared && NOTARY[declared] ? declared : 2800)); if (!NOTARY[chain]) throw new Error('unknown chain ' + chain + ' (2800 or 28001)'); if (opt.offline && (opt.rpc || opt.live)) throw new Error('--offline excludes --rpc and --live: offline, nothing is read from the chain or from Bitcoin'); return { chain, rpcGiven: !!opt.rpc, rpc: opt.rpc || RPC_DEFAULT[chain], notary: (opt.notary || NOTARY[chain]).toLowerCase(), manifests: opt.manifests || MANIFESTS_DEFAULT[chain], live: !!opt.live, btcAntet: opt.btcAntet || null, offline: !!opt.offline }; } /** every level present, the verdict, and the exit code main() would give: 0 all passed, 1 a level failed, 2 one could not be measured */ export async function judge(env, o, notJson = null) { const martorSingur = witnessFile(env); let levels; if (martorSingur) { levels = [await witness(env, env.leaf.hash, o)]; levels.push(await finality({ statementHash: String(env.leaf.hash), notarization: { chainId: o.chain, txHash: env.leaf.txHash } }, o)); } else { const l = local(env, notJson); levels = [...l.out]; if (l.text && env && typeof env === 'object') { levels.push(signature(env, l.text)); levels.push(await finality(env, o)); if (env.bitcoinWitness !== undefined) levels.push(await witness(env.bitcoinWitness, env.statementHash, o)); } } const failed = levels.some((x) => x.state === 'FAILED'); const unmeasured = levels.some((x) => x.state === 'UNMEASURED' || x.state === 'PENDING'); const verdict = failed ? 'INVALID' : unmeasured ? 'PARTIAL' : 'VALID'; const shown = martorSingur ? String(env.leaf.hash) : env && typeof env === 'object' && typeof env.statementHash === 'string' ? env.statementHash : null; return { verdict, statementHash: shown, levels, exitCode: failed ? 1 : unmeasured ? 2 : 0 }; } /** the text of a file, as an envelope: JSON (a leading byte order mark is skipped), or `notJson` naming why it is not */ export function parseEnvelope(raw) { try { return { env: JSON.parse(raw.charCodeAt(0) === 0xFEFF ? raw.slice(1) : raw), notJson: null }; } catch (e) { return { env: undefined, notJson: 'the file is not JSON (' + String(e.message).split('\n')[0].slice(0, 80) + ')' }; } } // 2026-10-02 (AIP-24, L1): the finality path and its helpers are exported so that verify-shield.mjs (tools/aere-quantum-shield) // judges a period head H_n through the SAME function, on a live endpoint or on a captured package replayed locally, instead of a // second copy of this logic. Run as a program, this file behaves exactly as before; imported, it runs nothing. if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) await main(); async function main() { const args = process.argv.slice(2); const opt = (n) => { const i = args.indexOf(n); return i >= 0 ? args[i + 1] : null; }; const FARA_VALOARE = new Set(['--json', '--live', '--offline']); const file = args.find((a, i) => !a.startsWith('--') && !(i > 0 && args[i - 1].startsWith('--') && !FARA_VALOARE.has(args[i - 1]))); if (!file) { console.error('usage: node verify-proof.mjs [--rpc ] [--chain 2800|28001] [--notary
] [--manifests ] [--live] [--btc-antet ] [--offline] [--json]'); stop(2); } let raw; try { raw = fs.readFileSync(file, 'utf8'); } catch (e) { console.error('the envelope cannot be read: ' + e.message); stop(2); } // a file that opens and is not JSON is not an envelope: refused at the form level, by name const { env, notJson } = parseEnvelope(raw); let o; try { o = options(env, { chain: opt('--chain'), rpc: opt('--rpc'), notary: opt('--notary'), manifests: opt('--manifests'), live: args.includes('--live'), btcAntet: opt('--btc-antet'), offline: args.includes('--offline') }); } catch (e) { console.error(e.message); stop(2); } const r = await judge(env, o, notJson); if (args.includes('--json')) console.log(JSON.stringify({ verdict: r.verdict, statementHash: r.statementHash, levels: r.levels }, null, 1)); else { console.log(`AERE Proof Protocol - ${path.basename(file)}`); for (const x of r.levels) console.log(` [${x.state.padEnd(10)}] ${x.level}: ${x.detail}`); console.log(`VERDICT: ${r.verdict}`); } process.exitCode = r.exitCode; }