// Controlul negativ al portofelului de plati x402 (proba-wallet.mjs), al modului cosign 2-din-2 (proba-cosign.mjs) si al verificatorului de plati // (proba-verifica-plati.mjs, inclusiv codul portofelului-contract): fiecare paznic se strica intr-o COPIE (agent-policy/*.mjs si // x402/*.mjs, in aceeasi asezare), proba ruleaza pe copie si trebuie sa iasa rosie EXACT pe verificarea numita, cu proba chiar rulata. // Plantarile sunt conditii false la rulare sau randuri scoase, deci copia se incarca intotdeauna; o ancora care nu apare exact o data, // sau o proba care nu ajunge la rezumat, e un esec al controlului (STRICAT), nu o linie informativa. // node control-negativ-wallet.mjs iesire 0 = martorul verde si toate plantarile rosii pe proba lor import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { spawn } from 'node:child_process'; import { createRequire } from 'node:module'; import { fileURLToPath } from 'node:url'; const AICI = path.dirname(fileURLToPath(import.meta.url)); const SUS = path.resolve(AICI, '..'); // ethers se rezolva de langa original (copia nu are node_modules); calea se da copiei prin AERE_ETHERS let ETHERS = process.env.AERE_ETHERS || null; // o copie a acestui dosar (controlul portii) primeste calea din mediu if (!ETHERS) try { ETHERS = createRequire(path.join(AICI, 'wallet.mjs')).resolve('ethers'); } catch { try { ETHERS = createRequire(path.join(AICI, 'wallet.mjs')).resolve(path.resolve(SUS, '..', '..', 'contracts', 'node_modules', 'ethers')); } catch { console.log('NEMASURAT: ethers nu se gaseste (npm install aici, sau AERE_ETHERS=)'); process.exit(2); } } const V = 'proba-verifica-plati.mjs', K = 'proba-cosign.mjs'; const PLANTARI = [ // [nume, fisier (relativ la x402/), tipar, inlocuire, textul verificarii care trebuie sa iasa rosie] ['cererile nu mai sunt judecate una cate una', 'wallet.mjs', 'function authorize(x) { const r = coada.then(() => autorizeaza(x)); coada = r.catch(() => {}); return r; }', 'function authorize(x) { return autorizeaza(x); }', '12. ATAC'], ['registrul nu mai trebuie sa verifice', 'wallet.mjs', ' if (!v.ok) {', " if (!v.ok && process.env.AERE_PLANTA_NICIODATA === 'da') {", '3. ATAC'], ['intrarea nu mai trebuie sa numeasca aceasta cumparatura', 'wallet.mjs', "for (const k of ['from', 'to', 'amount', 'asset', 'ref']) if (", 'for (const k of []) if (', '4. ATAC'], ['aceeasi intrare poate plati de doua ori', 'wallet.mjs', 'if (S.last && e.seq <= S.last.seq) return refuz(', "if (S.last && e.seq <= S.last.seq && process.env.AERE_PLANTA_NICIODATA === 'da') return refuz(", '5. ATAC'], ['intrarea nu mai trebuie sa fie de acum', 'wallet.mjs', 'if (!(Number(b.at) >= t - TOL && Number(b.at) <= t + 5)) return', "if (!(Number(b.at) >= t - TOL && Number(b.at) <= t + 5) && process.env.AERE_PLANTA_NICIODATA === 'da') return", '10. ATAC'], ['limita nu mai e judecata fata de ce a semnat portofelul', 'wallet.mjs', 'stare.signed.map((s) => ({ amount: s.amount, at: s.at }))', '[]', '7. politica noua'], ['aprobarile intrarii nu mai sunt date limitei portofelului', 'wallet.mjs', 'if (r.ok) aprobatori.push(r.humanId); }', '}', '9. 30000'], ['dovada de echivocare nu mai e data', 'wallet.mjs', 'if (p.agentId === policy.agentId && verifyEquivocation(p).ok) r.equivocation = p;', '', '6. si portofelul da dovada'], ['nonce-ul autorizarii nu mai numeste intrarea', 'wallet.mjs', 'nonce: nonceForEntry(e.hash) };', "nonce: '0x' + crypto.randomBytes(32).toString('hex') };", '1. nonce-ul fiecarei'], ['revocarile primite nu mai ajung la verificarea registrului', 'wallet.mjs', 'revocations: stare.revocations, anchors', 'revocations: [], anchors', '8. dupa revocare'], ['serverul de resurse primeste o plata pentru alta cerinta', 'resource-server.mjs', 'if (cheie(payload.accepted) !== cheie(requirement)) return cere(', "if (cheie(payload.accepted) !== cheie(requirement) && process.env.AERE_PLANTA_NICIODATA === 'da') return cere(", '11. CONTROL'], ['clientul cere semnatura si cand politica a refuzat', 'client.mjs', "if (!r.allowed) return { paid: false, status: 402, reason: `the agent's policy refused the payment: ${r.reason}`, entry: r.entry };", '', '2. CONTROL'], ['portofelul porneste si fara limita in activul lui', 'wallet.mjs', 'if (!policy.spend || policy.spend.asset !== assetId(network, token)) throw', "if ((!policy.spend || policy.spend.asset !== assetId(network, token)) && process.env.AERE_PLANTA_NICIODATA === 'da') throw", 'o politica fara limita'], // modul cosign (portofel-contract 2-din-2): ce verifica agentul inainte sa-si adauge jumatatea de semnatura ['agentul semneaza o autorizare din alt portofel', 'client.mjs', 'if (!eq(a.from, status.address)) return', 'if (false) return', 'schimba platitorul', K], ['agentul semneaza alt destinatar sau alta suma', 'client.mjs', 'if (!eq(a.to, req.payTo) || String(a.value) !== String(req.amount)) return', 'if (false) return', 'schimba destinatarul', K], ['agentul semneaza alt nonce decat intrarea lui', 'client.mjs', 'if (!eq(a.nonce, nonceForEntry(entryHash))) return', 'if (false) return', 'alt nonce', K], ['agentul semneaza un termen oricat de lung', 'client.mjs', 'if (!(Number(a.validBefore) > now && Number(a.validBefore) <= now + Number(req.maxTimeoutSeconds) + 60)) return', 'if (false) return', 'lungeste termenul', K], ['agentul nu mai compara digestul cu al lui', 'client.mjs', 'if (!cosign || digest !== cosign.digest) return', 'if (!cosign) return', 'alt digest', K], ['agentul nu mai cere jumatatea semnatarului declarat', 'client.mjs', 'if (!semnatarPolitica || !eq(semnatarPolitica, status.policySigner)) return', 'if (false) return', 'alta cheie decat semnatarul declarat', K], // verificatorul platilor, pe raspunsurile inregistrate de pe 28001 ['verificatorul nu mai cere ca registrul sa verifice', 'verifica-plati.mjs', 'entries)`, v.ok, v.error', 'entries)`, true, v.error', '2. CONTROL: o suma schimbata', V], ['verificatorul nu mai cere ca intrarea platii sa fie in registru', 'verifica-plati.mjs', '!!e && e.hash === p.entryHash)', '!!e)', '3. CONTROL', V], ['verificatorul nu mai cere portofelul in intrare', 'verifica-plati.mjs', "String(a.from).toLowerCase() === wallet && ", '', '4. CONTROL', V], ['verificatorul nu mai cere status 1', 'verifica-plati.mjs', "!!rc && rc.status === '0x1'", '!!rc', '5. CONTROL', V], ['verificatorul nu mai cere nonce-ul intrarii pe lant', 'verifica-plati.mjs', '&& l.topics[2].toLowerCase() === nonce));', '));', '6. CONTROL', V], ['verificatorul nu mai cere suma din Transfer', 'verifica-plati.mjs', '&& BigInt(l.data) === BigInt(a.amount)));', '));', '7. CONTROL', V], ['verificatorul nu mai cere ca orice Transfer sa fie in registru', 'verifica-plati.mjs', 'straine.length === 0,', 'true,', '8. CONTROL', V], ['verificatorul nu mai cere lantul dosarului', 'verifica-plati.mjs', 'lantul === chain,', 'true,', '9. CONTROL', V], // portofelul-contract 2-din-2 in dosar: codul de pe lant ['verificatorul nu mai judeca portofelul-contract', 'verifica-plati.mjs', ' if (d.walletContract) {', " if (d.walletContract && process.env.AERE_PLANTA_NICIODATA === 'da') {", '14. CONTROL', V], ['verificatorul cere doar lungimea codului, nu codul', 'verifica-plati.mjs', 'cod === asteptat, cod === asteptat ?', 'cod.length === asteptat.length, cod === asteptat ?', '12. CONTROL', V], ['codul asteptat nu mai poarta semnatarii din dosar', 'contract-2of2.mjs', 'cuvant.copy(cod, p.start); }', '}', '11. dosarul portofelului 2-din-2', V], ]; function copie() { const t = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-wallet-ctl-')); for (const f of fs.readdirSync(SUS).filter((n) => n.endsWith('.mjs'))) fs.copyFileSync(path.join(SUS, f), path.join(t, f)); fs.mkdirSync(path.join(t, 'x402')); // modulele si artefactul contractului 2-din-2 (verificatorul il citeste de langa el); nu package*.json si nu node_modules for (const f of fs.readdirSync(AICI).filter((n) => n.endsWith('.mjs') || n === 'AereAgentWallet2of2.json')) fs.copyFileSync(path.join(AICI, f), path.join(t, 'x402', f)); fs.cpSync(path.join(AICI, 'dovezi-28001'), path.join(t, 'x402', 'dovezi-28001'), { recursive: true }); return t; } // probele ruleaza cate PARALEL deodata (asincron), ca tot controlul sa incapa in termenul rulatorului comun const PARALEL = 4; function ruleaza(t, proba = 'proba-wallet.mjs') { return new Promise((resolve) => { const c = spawn(process.execPath, [path.join(t, 'x402', proba)], { env: { ...process.env, AERE_ETHERS: ETHERS } }); let out = ''; const ceas = setTimeout(() => c.kill(), 240000); c.stdout.on('data', (x) => { out += x; }); c.stderr.on('data', (x) => { out += x; }); c.on('close', (cod) => { clearTimeout(ceas); resolve({ cod, rulat: /(agent-wallet|verifica-plati|agent-cosign): \d+\/\d+/.test(out), rosii: out.split('\n').filter((l) => /^\s+RAU\s/.test(l)) }); }); }); } async function inGrup(lucrari) { const rez = new Array(lucrari.length); let i = 0; await Promise.all(Array.from({ length: PARALEL }, async () => { while (i < lucrari.length) { const k = i++; rez[k] = await lucrari[k](); } })); return rez; } let esecuri = 0; const martori = await inGrup(['proba-wallet.mjs', V, K].map((proba) => async () => { const t0 = copie(); try { return [proba, await ruleaza(t0, proba)]; } finally { fs.rmSync(t0, { recursive: true, force: true }); } })); for (const [proba, m0] of martori) { if (m0.cod === 0 && m0.rulat && !m0.rosii.length) console.log(` OK martorul ${proba}: copia neatinsa verde`); else { esecuri++; console.log(` RAU martorul ${proba} nu e verde (cod ${m0.cod}, ${m0.rulat ? m0.rosii.length + ' RAU' : 'nu a ajuns la rezumat'})`); } } const linii = await inGrup(PLANTARI.map(([nume, f, din, inl, tinta, proba = 'proba-wallet.mjs']) => async () => { let t = null; try { t = copie(); const fp = path.join(t, 'x402', f); if (!fs.existsSync(fp)) return [false, ` RAU ${nume}: plantarea numeste un fisier care nu exista (${f})`]; const src = fs.readFileSync(fp, 'utf8'); if (src.split(din).length !== 2) return [false, ` RAU ${nume}: tiparul apare de ${src.split(din).length - 1} ori in ${f} (STRICAT)`]; fs.writeFileSync(fp, src.replace(din, inl)); const r = await ruleaza(t, proba); if (!r.rulat) return [false, ` RAU ${nume}: proba nu a ajuns la rezumat (STRICAT, cod ${r.cod})`]; if (r.cod !== 0 && r.rosii.some((l) => l.includes(tinta))) return [true, ` OK ${nume}: '${tinta}' ROSIE (${r.rosii.length} RAU)`]; return [false, ` RAU ${nume}: '${tinta}' a ramas verde (${r.rosii.length} RAU altundeva)`]; } catch (e) { return [false, ` RAU ${nume}: controlul a cazut pe ea (${String(e.message).slice(0, 80)})`]; } finally { if (t) fs.rmSync(t, { recursive: true, force: true }); } })); for (const [bun, l] of linii) { console.log(l); if (!bun) esecuri++; } console.log(esecuri ? `RAU: ${esecuri} esecuri ale controlului` : `DOVEDIT: martorii verzi, ${PLANTARI.length} din ${PLANTARI.length} plantari rosii pe verificarea lor`); process.exitCode = esecuri ? 1 : 0;