#!/usr/bin/env node // raport.mjs (quantum exposure report, C12): the command line that produces the QUANTUM EXPOSURE REPORT of a list of Bitcoin and // Ethereum addresses, in one of three DECLARED modes, as a machine-readable JSON, a human-readable Markdown and an AIP-23 // `compliance` envelope whose evidenceHash binds the JSON (verifiable with the published reference verifier). // // mode 1 offline: by script / address type only. Reads no chain data, opens no connection. Cannot see spends. // mode 2 with the client's OWN node or index, through one adapter: --electrum host:port | --esplora url | --bitcoind url // [--txids file] | --eth-rpc url [--eth-blocks N|a..b]. Connections go only to that declared target. // mode 3 with a THIRD-PARTY provider: the same adapters, only with --consimtamant-tert ""; the target must // be a public service (a private or loopback target is refused) and the addresses are sent to it. // Every address gets exactly one of two labels, "exposed (on-chain evidence)" or "no on-chain evidence of exposure", or the state // "unmeasured" with a named reason; each verdict lists what was seen and what was not. Three questions about what no chain read here // shows (shared extended public keys; signed messages, PSBTs, EIP-191/712 signatures; the same key on another chain) are asked and the // answers are recorded as DECLARATIONS, never as measurements. The deadlines cited come from the compliance-report module beside the control plane, with // the document, its version and the date it was read. // // node raport.mjs --mod 1|2|3 --adrese [adapter flags] [--consimtamant-tert "text"] --iesire // [--data YYYY-MM-DD] [--subiect ] [--xpub-partajat da|nu|necunoscut] // [--semnaturi-offchain da|nu|necunoscut] [--alte-lanturi da|nu|necunoscut] [--cnsa] [--json] // : one line per address: a Bitcoin address (base58check, bech32, bech32m), `script:`, // `p2pk:`, or an Ethereum address (0x + 40 hex; mixed case is checked as EIP-55). `#` starts a comment. // --data: the day the report records (default: the UTC day of the run); a day after the UTC day of the run is refused, and with a day // in the past no verdict rests on a block the target served after the end of that day (clasifica.mjs) // Exit: 0 report written, every address classified; 2 report written, some addresses unmeasured; 1 refused (nothing written). // Nothing about the owner is recorded: the addresses appear, nothing else. No value of any environment variable is printed. import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { ETICHETA, NEMASURAT, verificaLimbaj } from './limbaj.mjs'; // the cryptographic dependencies (package.json) are loaded first, through noble.mjs: when they are missing the tool names them and // exits 2 ("could not run") instead of failing inside an import try { await import('./noble.mjs'); } catch (e) { console.error(String((e && e.message) || e)); process.exit(2); } const { decodeazaIntrareBitcoin } = await import('./bitcoin.mjs'); const { decodeazaAdresaEth } = await import('./ethereum.mjs'); const C = await import('./clasifica.mjs'); const { ADAPTOARE, Refuz, rezolvaPublic, tintaAdaptorului, tranzactiiCore, lantulCore } = await import('./adaptoare.mjs'); const AICI = path.dirname(fileURLToPath(import.meta.url)); const primul = (lista) => lista.filter(Boolean).map((p) => path.resolve(AICI, p)).find((p) => fs.existsSync(p)); const TERMENE = primul([process.env.AERE_EXPUNERE_TERMENE, '../../../cloud-gateway/control-plane/raport-conformitate.mjs', '../../../aere-quantum/control-plane/raport-conformitate.mjs', '../control-plane/raport-conformitate.mjs']); const PK = primul([process.env.AERE_EXPUNERE_PROOF_KINDS, '../../proof-kinds/proof-kinds.mjs', '../../../aere-quantum/proof-kinds/proof-kinds.mjs', '../proof-kinds/proof-kinds.mjs']); if (!TERMENE || !PK) { console.error('raport.mjs: the compliance-report module (deadlines) or the proof-kinds builder is not beside this tool'); process.exit(1); } const { REGIMURI, canonicJson, sha256Canonic } = await import(pathToFileURL(TERMENE).href); const { buildProof } = await import(pathToFileURL(PK).href); export const VERSIUNE = 'aere-quantum/exposure-report/1 (2026-10-12)'; export const INTREBARI = Object.freeze({ sharedXpub: 'Has an extended public key (xpub / ypub / zpub, or any account-level public key) of a wallet holding any of these addresses ever been given to a third party: a service, an exchange, a companion application, a co-signer, a backup? With such a key, once a quantum computer recovers one non-hardened child key, the parent private key and every sibling key follow (BIP-32).', offChainSignatures: 'Have any of the keys behind these addresses signed anything off chain: Bitcoin signed messages (BIP-137, BIP-322), PSBTs or partially signed transactions sent to others, Ethereum EIP-191 or EIP-712 signatures (token permits, exchange orders, sign-in messages), or a transaction that was broadcast and never confirmed (replaced by fee, dropped from mempools)? Such a signature discloses the public key exactly as a confirmed spend would, and no chain shows it.', otherChains: 'Has the same key, or the same address, signed on another chain: any other EVM chain where the same address is the same account (a layer 2, a sidechain, a test network), a Bitcoin fork that shares the history before its split, a Bitcoin test network, or a chain of another kind that uses the same secp256k1 key? A signature there discloses the public key exactly as one here would, and this report read only the chain named in each verdict.', }); const RASPUNS = { da: 'yes (declared)', nu: 'no (declared)', necunoscut: 'unknown (declared)' }; // ---------------------------------------------------------------- arguments function argumente(argv) { const o = { _: [] }; for (let i = 0; i < argv.length; i++) { const t = argv[i]; const val = (k) => { if (i + 1 >= argv.length) throw new Refuz('usage', `${k} needs a value`); return argv[++i]; }; if (t === '--mod') o.mod = val(t); else if (t === '--adrese') o.adrese = val(t); else if (t === '--iesire') o.iesire = val(t); else if (t === '--electrum') o.electrum = val(t); else if (t === '--esplora') o.esplora = val(t); else if (t === '--bitcoind') o.bitcoind = val(t); else if (t === '--eth-rpc') o['eth-rpc'] = val(t); else if (t === '--eth-blocks') o.ethBlocks = val(t); else if (t === '--txids') o.txids = val(t); else if (t === '--consimtamant-tert') o.consimtamant = val(t); else if (t === '--data') o.data = val(t); else if (t === '--subiect') o.subiect = val(t); else if (t === '--xpub-partajat') o.xpub = val(t); else if (t === '--semnaturi-offchain') o.offchain = val(t); else if (t === '--alte-lanturi') o.alteLanturi = val(t); else if (t === '--cnsa') o.cnsa = true; else if (t === '--json') o.json = true; else if (t === '--help' || t === '-h') o.help = true; else throw new Refuz('usage', 'unknown argument ' + t); } return o; } const USAGE = `usage: node raport.mjs --mod 1|2|3 --adrese --iesire [--electrum host:port | --esplora | --bitcoind [--txids ] | --eth-rpc [--eth-blocks N|a..b]] [--consimtamant-tert ""] [--data YYYY-MM-DD] [--subiect