// bytecode.js, opcode-aware static analysis of EVM runtime bytecode for // quantum exposure. Zero dependencies. // // Two techniques make this honest rather than a grep: // // 1. Opcode-aware decoding. A naive search for "610ae1" (PUSH2 0x0AE1) in the // hex produces false positives, because those bytes can appear inside // another PUSH's immediate data or straddle an instruction boundary. The // decoder walks the instruction stream, skipping each PUSH's immediate, so a // matched constant is a genuine PUSH of that value. // // 2. Local stack simulation to resolve CALL targets. The real question is not // "does the constant 0x0AE1 appear" but "is a CALL/STATICCALL actually made // TO that address". We simulate the stack per basic block (reset at // JUMPDEST / after JUMP,JUMPI / after terminators) tracking constant PUSH // values, and at every CALL-family opcode we read the address argument (the // 2nd stack item). This distinguishes a precompile call to 0x0AE1 from the // bare number 0x0AE5 used as a memory length near an unrelated staticcall // (the false positive a proximity heuristic would hit). // // Fallback: the constants 0x0AE1..0x0AE4 (2785..2788) are not round numbers or // common offsets, so a genuine opcode-aligned PUSH of one is a medium-confidence // signal even when the CALL target is loaded from memory and cannot be resolved // locally. The common literals 0x01, 0x100 and 0x0AE5 are NEVER used as a // fallback signal; they only count when stack simulation resolves them as an // actual CALL target. import { CLASSICAL, PQC, knownAddressTable } from './precompiles.js'; const CALL_NAME = { 0xf1: 'CALL', 0xf2: 'CALLCODE', 0xf4: 'DELEGATECALL', 0xfa: 'STATICCALL' }; const CALL_OPS = new Set([0xf1, 0xf2, 0xf4, 0xfa]); // pops before the address arg is consumed: CALL/CALLCODE take (gas,addr,value,..) // STATICCALL/DELEGATECALL take (gas,addr,..); in all four addr is the 2nd item. const CALL_ARITY = { 0xf1: 7, 0xf2: 7, 0xf4: 6, 0xfa: 6 }; const HALTS = new Set([0x00, 0xf3, 0xfd, 0xfe, 0xff]); // STOP RETURN REVERT INVALID SELFDESTRUCT // (pops, pushes) for opcodes whose stack effect we model to keep alignment. // PUSH/DUP/SWAP/CALL-family are handled specially and are not in this table. const ARITY = { 0x01: [2, 1], 0x02: [2, 1], 0x03: [2, 1], 0x04: [2, 1], 0x05: [2, 1], 0x06: [2, 1], 0x07: [2, 1], 0x08: [3, 1], 0x09: [3, 1], 0x0a: [2, 1], 0x0b: [2, 1], 0x10: [2, 1], 0x11: [2, 1], 0x12: [2, 1], 0x13: [2, 1], 0x14: [2, 1], 0x15: [1, 1], 0x16: [2, 1], 0x17: [2, 1], 0x18: [2, 1], 0x19: [1, 1], 0x1a: [2, 1], 0x1b: [2, 1], 0x1c: [2, 1], 0x1d: [2, 1], 0x20: [2, 1], 0x30: [0, 1], 0x31: [1, 1], 0x32: [0, 1], 0x33: [0, 1], 0x34: [0, 1], 0x35: [1, 1], 0x36: [0, 1], 0x37: [3, 0], 0x38: [0, 1], 0x39: [3, 0], 0x3a: [0, 1], 0x3b: [1, 1], 0x3c: [4, 0], 0x3d: [0, 1], 0x3e: [3, 0], 0x3f: [1, 1], 0x40: [1, 1], 0x41: [0, 1], 0x42: [0, 1], 0x43: [0, 1], 0x44: [0, 1], 0x45: [0, 1], 0x46: [0, 1], 0x47: [0, 1], 0x48: [0, 1], 0x49: [1, 1], 0x4a: [0, 1], 0x50: [1, 0], 0x51: [1, 1], 0x52: [2, 0], 0x53: [2, 0], 0x54: [1, 1], 0x55: [2, 0], 0x56: [1, 0], 0x57: [2, 0], 0x58: [0, 1], 0x59: [0, 1], 0x5a: [0, 1], 0x5b: [0, 0], 0x5c: [1, 1], 0x5d: [2, 0], 0x5e: [3, 0], 0xa0: [2, 0], 0xa1: [3, 0], 0xa2: [4, 0], 0xa3: [5, 0], 0xa4: [6, 0], 0xf0: [3, 1], 0xf5: [4, 1], }; /** * Decode runtime bytecode into a flat instruction list. * @param {Uint8Array} code */ export function decode(code) { const ops = []; let pc = 0; let i = 0; while (pc < code.length) { const op = code[pc]; if (op >= 0x60 && op <= 0x7f) { const n = op - 0x5f; let val = 0n; for (let k = 1; k <= n && pc + k < code.length; k++) val = (val << 8n) | BigInt(code[pc + k]); ops.push({ i: i++, pc, op, push: val, pushLen: n }); pc += 1 + n; } else { ops.push({ i: i++, pc, op }); pc += 1; } } return ops; } /** * Resolve CALL-family targets via per-basic-block local stack simulation. * @returns {Array<{op:number, name:string, pc:number, target:bigint|null}>} */ export function resolveCallTargets(ops) { const calls = []; let stack = []; // items: bigint (known) or null (unknown) const pop = () => (stack.length ? stack.pop() : null); for (const o of ops) { const op = o.op; if (op === 0x5b) { stack = []; continue; } // JUMPDEST starts a new block if (op >= 0x60 && op <= 0x7f) { stack.push(o.push); continue; } // PUSHn (0x5f PUSH0 handled below) if (op === 0x5f) { stack.push(0n); continue; } // PUSH0 if (op >= 0x80 && op <= 0x8f) { // DUPk const k = op - 0x80 + 1; stack.push(stack.length >= k ? stack[stack.length - k] : null); continue; } if (op >= 0x90 && op <= 0x9f) { // SWAPk const k = op - 0x90 + 1; const idx = stack.length - 1 - k; if (stack.length >= 1 && idx >= 0) { const top = stack.length - 1; const tmp = stack[top]; stack[top] = stack[idx]; stack[idx] = tmp; } continue; } if (CALL_OPS.has(op)) { // address is the 2nd stack item from the top (below gas) const target = stack.length >= 2 ? stack[stack.length - 2] : null; calls.push({ op, name: CALL_NAME[op], pc: o.pc, target }); for (let k = 0; k < CALL_ARITY[op]; k++) pop(); stack.push(null); // success flag if (op === 0x56 || op === 0x57) stack = []; continue; } if (op === 0x56 || op === 0x57) { // JUMP / JUMPI const arity = ARITY[op]; for (let k = 0; k < arity[0]; k++) pop(); stack = []; // next instruction is a new block continue; } if (HALTS.has(op)) { const a = ARITY[op]; if (a) for (let k = 0; k < a[0]; k++) pop(); stack = []; continue; } const a = ARITY[op]; if (a) { for (let k = 0; k < a[0]; k++) pop(); for (let k = 0; k < a[1]; k++) stack.push(null); } else { // unknown opcode: be conservative, drop the block alignment stack = []; } } return calls; } /** * Analyze runtime bytecode for classical and post-quantum precompile usage. * @param {Uint8Array} code runtime bytecode (empty => caller treats as EOA) */ // SINGURA SURSA a vocabularului de verdicte. Exportata anume, fiindca doua consumatoare si-au // scris fiecare propria lista si una a scris AMBER acolo unde scanerul scrie YELLOW: un contract // hibrid, adica exact clientul care a inceput deja migrarea si e cel mai probabil sa plateasca, // cadea in NOT MEASURED atat in raportul platit cat si in scanul gratuit. Masurat 2026-08-16. // Cine adauga un verdict il adauga AICI, si consumatoarele il primesc fara sa fie atinse. export const VERDICTE = Object.freeze(['GREEN', 'YELLOW', 'RED']); // Cum se citeste verdictul dintr-o iesire de text. Consumatoarele cheama ASTA, nu isi scriu // propriul tipar: un tipar copiat e a doua sursa a aceluiasi adevar, si exact asta a fost // defectul. Nu foloseste expresii regulate, ca sa nu existe nici macar un sir de escapat gresit. // Intoarce numele verdictului, sau null daca textul nu poarta niciunul. export function citesteVerdict(text) { for (const linie of String(text).split(/\r?\n/)) { const cuvant = linie.trim().split(/[^A-Z]+/).filter(Boolean)[0]; if (cuvant && VERDICTE.includes(cuvant)) return cuvant; } return null; } export function analyzeBytecode(code) { const ops = decode(code); const table = knownAddressTable(); const calls = resolveCallTargets(ops); // Resolved CALL targets that land on a known band address (definitive). const resolved = calls .filter((c) => c.target !== null && table.has(c.target)) .map((c) => ({ ...table.get(c.target), address: '0x' + c.target.toString(16), callOpcode: c.name, pc: c.pc })); const pqcResolved = resolved.filter((r) => r.band === 'pqc-live'); const pqcTestnetResolved = resolved.filter((r) => r.band === 'pqc-testnet'); const ecrecoverResolved = resolved.filter((r) => r.key === 'ecrecover'); const p256Resolved = resolved.filter((r) => r.key === 'p256'); // Fallback presence signal: an opcode-aligned PUSH of a SIGNATURE-verify PQC // address 0x0AE1..0x0AE4 (not 0x0AE5, not the common literals). Medium // confidence: the constant is genuinely present even if we could not resolve // the CALL target locally (e.g. it was staged through memory). const sigPqcAddrs = new Set([PQC.falcon512.address, PQC.falcon1024.address, PQC.mldsa44.address, PQC.slhdsa128s.address]); const pqcConstPresent = []; for (const o of ops) { if (o.push !== undefined && sigPqcAddrs.has(o.push)) { const d = table.get(o.push); pqcConstPresent.push({ address: '0x' + o.push.toString(16), name: d.name, pc: o.pc }); } } const uniqByAddr = (arr) => { const m = new Map(); for (const x of arr) if (!m.has(x.address)) m.set(x.address, x); return [...m.values()]; }; const pqcResolvedU = uniqByAddr(pqcResolved); const pqcConstU = uniqByAddr(pqcConstPresent).filter((c) => !pqcResolvedU.find((r) => r.address === c.address)); const pqcUsed = pqcResolvedU.length > 0 || pqcConstU.length > 0; const classicalUsed = ecrecoverResolved.length > 0 || p256Resolved.length > 0; let readiness; if (pqcUsed && classicalUsed) readiness = 'YELLOW'; else if (pqcUsed) readiness = 'GREEN'; else readiness = 'RED'; const pqcLivePrecompiles = [ ...pqcResolvedU.map((r) => ({ address: r.address, name: r.name, callProximate: true, confidence: 'high' })), ...pqcConstU.map((c) => ({ address: c.address, name: c.name, callProximate: false, confidence: 'medium' })), ]; return { kind: 'contract', readiness, bytecodeLength: code.length, signals: { pqcLivePrecompiles, pqcTestnetPrecompiles: uniqByAddr(pqcTestnetResolved).map((r) => ({ address: r.address, name: r.name })), p256: p256Resolved.length > 0 ? { address: '0x100', name: CLASSICAL.p256.name, count: p256Resolved.length, confidence: 'high' } : null, ecrecover: ecrecoverResolved.length > 0 ? { address: '0x1', name: CLASSICAL.ecrecover.name, count: ecrecoverResolved.length, confidence: 'high' } : null, }, flags: buildFlags({ pqcResolvedU, pqcConstU, pqcTestnetResolved, pqcUsed }), detail: { callsAnalyzed: calls.length, resolvedBandCalls: resolved.length, }, }; } function buildFlags({ pqcResolvedU, pqcConstU, pqcTestnetResolved, pqcUsed }) { const flags = []; if (pqcConstU.length > 0 && pqcResolvedU.length === 0) { flags.push('[VERIFY] A live PQC precompile address (0x0AE1..0x0AE4) is pushed but no CALL to it was resolved by local stack analysis; the call target may be staged through memory. Confirm against source or a call trace.'); } if (pqcTestnetResolved.length > 0) { flags.push('[VERIFY] Call to a TESTNET-only PQC precompile (0x0AE6..0x0AE8); these are NOT live on mainnet 2800 and return empty there.'); } if (!pqcUsed) { flags.push('Bytecode scanning only sees NATIVE precompile calls (0x0AE1..0x0AE5). A contract that verifies Falcon via the SOLIDITY AereFalcon512Verifier (0x4E8e...D8fFC) delegates to a normal contract address and will read as RED here even though it is PQC-capable. Confirm the auth path against source.'); } return flags; }