// scanner.js, the account-level readiness classifier. Ties the RPC fetch and // the bytecode analysis together and produces the RED / YELLOW / GREEN report. // Zero dependencies. import { analyzeBytecode } from './bytecode.js'; import { hexToBytes } from './keccak.js'; import { getCode, getChainId } from './rpc.js'; const ADDR_RE = /^0x[0-9a-fA-F]{40}$/; export function isValidAddress(a) { return typeof a === 'string' && ADDR_RE.test(a); } /** * Classify an EOA. An externally owned account is authenticated by a * secp256k1 ECDSA key: quantum-vulnerable, and (unlike a contract) it CANNOT * host post-quantum verification logic. Always RED. The migration path for an * EOA is to move control to a PQC or hybrid smart account (see the migration * SDK), optionally via EIP-7702 delegation. */ export function classifyEoa(address) { return { address, kind: 'eoa', readiness: 'RED', reason: 'Externally owned account: authenticated by a secp256k1 ECDSA key, which Shor\'s algorithm breaks. An EOA holds no code, so it cannot verify a post-quantum signature itself.', signals: { pqcLivePrecompiles: [], pqcTestnetPrecompiles: [], p256: null, ecrecover: { address: '0x1', name: 'ECDSA secp256k1 (implicit EOA authentication)', confidence: 'certain' }, }, flags: [], migration: 'Move control to a PQC smart account (AerePQCAccount, Falcon-512 owned) or a hybrid account (AereHybridAuth, ECDSA + Falcon-512). See the migration SDK.', }; } /** * Analyze contract bytecode already in hand (offline path). `code` is a * 0x-prefixed hex string. */ export function scanBytecode(address, codeHex) { const bytes = hexToBytes(codeHex); if (bytes.length === 0) return classifyEoa(address); const analysis = analyzeBytecode(bytes); return { address, ...analysis }; } /** * Live scan: fetch code over RPC, then classify. [MEASURE] path, requires a * reachable RPC. Falls back to a clear error the CLI can present. * @returns {Promise} report with a `.live` block */ export async function scanLive(address, rpcUrl, { timeoutMs } = {}) { if (!isValidAddress(address)) throw new Error(`invalid address: ${address}`); const chainId = await getChainId(rpcUrl, timeoutMs); const code = await getCode(rpcUrl, address, timeoutMs); const report = scanBytecode(address, code); report.live = { rpcUrl, chainId, codeBytes: (code.length - 2) / 2 }; return report; }