#!/usr/bin/env node 'use strict'; /* * raport.js - produces an Aere Quantum Exposure Report from live on-chain measurement. * * WHY THIS FILE EXISTS AND WHAT MAKES IT WORTH PAYING FOR. Anyone can write a PDF that says * "you are exposed to quantum risk". What a buyer cannot get elsewhere is a report that: * * - names, for every address, what was MEASURED and what was NOT, and never blurs the two; * - carries the exact command that reproduces it, so the buyer can re-run it next quarter, * on a node they run themselves, and get the same answer or a documented difference; * - carries a manifest of digests so a changed report is detectable; * - answers the questions a supervisor actually asks, in the wording of the published * criteria, instead of inventing a scoring scheme. * * A report that cannot be reproduced is an opinion. This one is a measurement with a receipt. * * node raport.js 0xAddr [0xAddr...] --rpc https://... [--client "Name"] [--out dir] * node raport.js --fixture fixtures/x.json --out dir # offline, for demos */ import { execFileSync } from 'node:child_process'; import crypto from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { citesteVerdict } from './lib/bytecode.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const argv = process.argv.slice(2); const arg = (n, d) => { const i = argv.indexOf(n); return i >= 0 && i + 1 < argv.length ? argv[i + 1] : d; }; const has = (n) => argv.includes(n); const ADRESE = argv.filter((a) => /^0x[0-9a-fA-F]{40}$/.test(a)); const RPC = arg('--rpc', null); const CLIENT = arg('--client', 'the holder of this report'); const OUT = arg('--out', 'raport-' + new Date().toISOString().slice(0, 10).replace(/-/g, '')); const FIXTURE = arg('--fixture', null); if (!ADRESE.length && !FIXTURE) { console.error('folosire: node raport.js 0xAddr [0xAddr...] --rpc URL [--client "Nume"] [--out dir]'); process.exit(2); } const sha = (s) => crypto.createHash('sha256').update(s).digest('hex'); function scaneaza(adr) { const a = ['scan.js', adr]; if (RPC) a.push('--rpc', RPC); try { const out = execFileSync(process.execPath, a, { cwd: __dirname, encoding: 'utf8', timeout: 120000 }); return { adr, ok: true, text: out.trim() }; } catch (e) { return { adr, ok: false, text: String((e.stdout || '') + (e.stderr || e.message)).trim() }; } } // Reads the scan text back into the two things a buyer pays for: what was measured, and what the // tool itself says it could not settle. The tool marks the latter with [VERIFY] or [MEASURE], and // those lines are carried through verbatim rather than summarised away. function citeste(text) { // Verdictul se citeste prin functia comuna din lib/bytecode.js, NU printr-un tipar scris aici. // Aici a fost defectul: acest fisier cauta AMBER, scanerul scrie YELLOW, deci fiecare contract // hibrid, adica exact clientul care a inceput deja migrarea, cadea in NOT MEASURED. const stare = citesteVerdict(text) || 'NOT MEASURED'; const precomp = [...text.matchAll(/^\s*-\s*(0x[0-9a-f]+)\s+(.+?)\s*(\[[a-z]+\])?$/gim)].map((m) => m[1] + ' ' + m[2].trim()); const nemasurat = [...text.matchAll(/^\s*\*\s*(\[(?:VERIFY|MEASURE)\][^\n]*)/gim)].map((m) => m[1].trim()); const cod = (text.match(/code:\s*(\d+)\s*bytes/) || [])[1] || null; return { stare, precomp, nemasurat, cod }; } const rezultate = ADRESE.map((a) => { const r = scaneaza(a); return { ...r, ...citeste(r.text) }; }); const acum = new Date().toISOString().replace('T', ' ').slice(0, 16) + 'Z'; const comanda = 'node raport.js ' + ADRESE.join(' ') + (RPC ? ' --rpc ' + RPC : ''); const nrRosu = rezultate.filter((r) => r.stare === 'RED').length; const nrGalben = rezultate.filter((r) => r.stare === 'YELLOW').length; const nrVerde = rezultate.filter((r) => r.stare === 'GREEN').length; const nrNem = rezultate.filter((r) => r.stare === 'NOT MEASURED').length; const totalNem = rezultate.reduce((s, r) => s + r.nemasurat.length, 0); let md = `# Aere Quantum Exposure Report **Prepared for:** ${CLIENT} **Measured:** ${acum} **Scope:** ${ADRESE.length} on-chain address${ADRESE.length === 1 ? '' : 'es'}${RPC ? ', read from ' + RPC : ', offline fixture'} ## Read this page first This report states what was **measured** and what was **not**. Those are different, and mixing them is how exposure reports become useless. Every line below is one or the other, and the not-measured lines say why. **It is reproducible.** Run this and compare: ${comanda} Point \`--rpc\` at a node you run yourself. Nothing here depends on trusting the party that wrote this report, and that is the point: an exposure report you cannot re-run is an opinion with a logo on it. ## What was found | address | verdict | code | live PQ verifiers reachable | open questions | |---|---|---|---|---| ${rezultate.map((r) => `| \`${r.adr}\` | **${r.stare}** | ${r.cod ? r.cod + ' B' : 'n/a'} | ${r.precomp.length} | ${r.nemasurat.length} |`).join('\n')} Totals: ${nrRosu} RED, ${nrGalben} YELLOW, ${nrVerde} GREEN, ${nrNem} not measured. **${totalNem} question${totalNem === 1 ? '' : 's'} the tool refused to settle on its own** are listed per address below. A tool that never says "I could not settle this" is not being careful, it is being quiet. ## Per address ${rezultate.map((r) => `### \`${r.adr}\` - ${r.stare} ${r.precomp.length ? 'Post-quantum verifiers reachable from this bytecode:\n' + r.precomp.map((p) => '- ' + p).join('\n') : 'No live post-quantum verifier was resolved from this bytecode.'} ${r.nemasurat.length ? '**Not settled by measurement, carried through verbatim:**\n' + r.nemasurat.map((n) => '- ' + n).join('\n') : '_Nothing was left unsettled for this address._'}
raw tool output \`\`\` ${r.text} \`\`\`
`).join('\n')} ## Answers to the questions a supervisor asks These are written against **published criteria**, quoted as criteria and not as a certification. This report is not an accredited audit and does not claim to be one. **"Which of your on-chain components already depend on quantum-vulnerable signatures?"** Every address in scope authorises through ECDSA secp256k1, because that is what an EVM account is. The measured question is a different one: whether a component can *also* verify a post-quantum signature, and ${rezultate.filter((r) => r.precomp.length).length} of ${ADRESE.length} address${ADRESE.length === 1 ? '' : 'es'} in scope reach a live NIST post-quantum verifier. **"Do you have a migration path that does not require redeploying everything?"** Measured per address above. Where a component routes verification through a registry rather than a hardcoded address, an algorithm can be added or retired without redeploying the component. That property is visible in bytecode and is reported as such, not assumed. **"What is the cost, and who pays it?"** The toolkit's \`plan.js\` produces per-account gas figures from measured deployed-account costs. Cost is quoted from measurement, never from a vendor estimate. **"How would an auditor check your claim next year, without you?"** By running the command at the top of this report against their own node. That is the entire answer, and it is the reason this report has a command in it. ## What this report does NOT say - It is **not** an accredited audit, and we are **not** an accredited audit firm. It measures the on-chain slice: bytecode and what it can reach. Repositories, TLS, dependencies, key custody, HSMs and off-chain services are **out of scope** and unmeasured here. - It does **not** say "compliant with NIST". It says "measured against the criteria published in NIST IR 8547". Compliance is a statement only an accredited body can make. - It does **not** claim that a live post-quantum verifier makes an account quantum-safe. A post-quantum verifier called from a transaction that is itself authorised with ECDSA gives no post-quantum security: the adversary forges the outer transaction. Anyone selling you the opposite can be taken apart in five minutes. - It does **not** promise legal effect. What a timestamp or an attestation means in court depends on jurisdiction and is not asserted here. - "harvest now, decrypt later" does **not** apply to signatures: a signature is public and is not harvested. The threat that applies to a chain is retroactive rewriting of history with keys recovered later, which is a different and more serious problem. ## Integrity Digests of every file in this report are in \`MANIFEST.sha256\`. Verify with: sha256sum -c MANIFEST.sha256 `; fs.mkdirSync(OUT, { recursive: true }); fs.writeFileSync(path.join(OUT, 'raport.md'), md); fs.writeFileSync(path.join(OUT, 'raport.json'), JSON.stringify({ format: 'aere-quantum-exposure/1', client: CLIENT, measuredAt: acum, rpc: RPC || null, command: comanda, addresses: rezultate.map((r) => ({ address: r.adr, verdict: r.stare, codeBytes: r.cod ? Number(r.cod) : null, pqVerifiers: r.precomp, notSettled: r.nemasurat })), totals: { red: nrRosu, amber: nrGalben, green: nrVerde, notMeasured: nrNem, openQuestions: totalNem }, }, null, 2)); const fisiere = fs.readdirSync(OUT).filter((f) => f !== 'MANIFEST.sha256'); fs.writeFileSync(path.join(OUT, 'MANIFEST.sha256'), fisiere.map((f) => sha(fs.readFileSync(path.join(OUT, f))) + ' ' + f).join('\n') + '\n'); console.log('Aere Quantum Exposure Report'); console.log(' adrese masurate : ' + ADRESE.length + ' RED ' + nrRosu + ', YELLOW ' + nrGalben + ', GREEN ' + nrVerde + ', NEMASURAT ' + nrNem); console.log(' intrebari pe care unealta a refuzat sa le inchida singura: ' + totalNem); console.log(' scris in : ' + OUT + '/ (raport.md, raport.json, MANIFEST.sha256)'); console.log(' reproductibil : ' + comanda); process.exit(0);