// Controlul negativ al arborelui Merkle al jurnalului (arbore-merkle.mjs, jurnal-arbore.mjs, proba-arbore.mjs): fiecare paznic // scos intr-o COPIE trebuie sa inroseasca proba NUMITA, cu proba chiar rulata; pe copia neatinsa, verde. Trei stari: un tipar care // nu apare exact o data sau o proba care nu ajunge la rezumat e STRICAT si se numara esec. // node control-negativ-arbore.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 { fileURLToPath } from 'node:url'; const AICI = path.dirname(fileURLToPath(import.meta.url)); const M = 'arbore-merkle.mjs', J = 'jurnal-arbore.mjs'; const PLANTARI = [ // [nume, fisier, [[tipar, inlocuire], ...], proba (inceputul numelui ei)] ['fara separarea frunza / nod', M, [['const Z = Buffer.from([0x00]), U = Buffer.from([0x01]);', 'const Z = Buffer.alloc(0), U = Buffer.alloc(0);']], 'CONTROL: o frunza nu poate fi data drept nod'], ['includerea nu mai cere ca drumul sa se termine la varf (sn == 0)', M, [[" return sn === 0 && hx(r) === String(rootHash);", " return hx(r) === String(rootHash);"]], 'CONTROL: un nod intern dat drept frunza'], ['includerea nu mai coboara pe ramura dreapta (forma gresita a algoritmului)', M, [[" r = nod(pb, r);\n if (!lsb(fn)) while (!lsb(fn) && fn !== 0) { fn >>= 1; sn >>= 1; }", ' r = nod(pb, r);']], 'fiecare dovada de includere'], ['consistenta nu mai cere radacina veche', M, [["return sn === 0 && hx(fr) === firstRoot && hx(sr) === secondRoot;", 'return sn === 0 && hx(sr) === secondRoot;']], 'ATAC: istoria rescrisa'], ['consistenta fara radacina veche pusa in fata la o putere a lui 2', M, [["if ((firstSize & (firstSize - 1)) === 0) pc.unshift(dinHex(firstRoot, 'first root'));", '']], 'fiecare dovada de consistenta'], ['marimi egale acceptate cu orice drum', M, [['if (firstSize === secondSize) return path.length === 0 && firstRoot === secondRoot;', 'if (firstSize === secondSize) return true;']], 'CONTROL: marimi egale'], ['intrarea din dovada nu isi mai reface hash-ul', J, [["if (hashIntrare(e.seq, e.prev, e.proof) !== e.hash) return", 'if (false) return']], 'ATAC: intrarea din dovada schimbata'], ['plicul intrarii nu mai trebuie sa fie intreg', J, [["if (!e.proof || !e.proof.statement || sha256(Buffer.from(JSON.stringify(e.proof.statement), 'utf8')) !== String(e.proof.statementHash).toLowerCase()) return", 'if (false) return']], 'ATAC: un plic stricat scris de gazda'], ['marimea si radacina capului nu se mai compara cu dovada', J, [['if (cap.statement.treeSize !== d.treeSize || cap.statement.rootHash !== d.rootHash) return', 'if (false) return']], 'ATAC: dovada de includere pentru un arbore de alta marime'], ['capul atins primit (statementHash nerefacut)', J, [["if (!H.test(String(cap.statementHash)) || sha256(Buffer.from(JSON.stringify(st), 'utf8')) !== cap.statementHash) return", 'if (false) return']], 'ATAC: capul de arbore atins'], ['semnatarul cerut nu mai trece in verificare', J, [['const s = verificaSemnaturaCap(cap, semnatar);', 'const s = verificaSemnaturaCap(cap, null);']], 'includere: intrarea 7'], ['dovada de consistenta nu mai trebuie sa porneasca de la capul vechi', J, [['if (vechi.statement.treeSize !== d.firstSize || vechi.statement.rootHash !== d.firstRoot) return', 'if (false) return']], 'ATAC: capetele inversate'], ['un jurnal rupt primeste cap', J, [['if (!v.ok) throw new Error(`tree: the log is broken', 'if (false) throw new Error(`tree: the log is broken']], 'un jurnal rupt'], // B-25: forma publicata in 082b862 (iesirea tace despre semnatura capetelor) ['verify-consistency tace despre semnatura capetelor', 'sidecar.mjs', [['the history up to the old head was not rewritten)${semn}', 'the history up to the old head was not rewritten)']], 'B-25'], ]; const FISIERE = [M, J, 'sidecar.mjs', 'proba-arbore.mjs']; function copie() { const t = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-arb-ctl-')); fs.mkdirSync(path.join(t, 'aere-verify-layer')); fs.mkdirSync(path.join(t, 'proof-kinds')); for (const f of FISIERE) fs.copyFileSync(path.join(AICI, f), path.join(t, 'aere-verify-layer', f)); fs.copyFileSync(path.join(AICI, '..', 'proof-kinds', 'proof-kinds.mjs'), path.join(t, 'proof-kinds', 'proof-kinds.mjs')); return t; } function ruleaza(t) { return new Promise((resolve) => { const c = spawn(process.execPath, [path.join(t, 'aere-verify-layer', 'proba-arbore.mjs')]); let out = ''; const ceas = setTimeout(() => c.kill(), 180000); c.stdout.on('data', (x) => { out += x; }); c.stderr.on('data', (x) => { out += x; }); c.on('close', (cod) => { clearTimeout(ceas); resolve({ cod, rulat: /aere-arbore: \d+\/\d+/.test(out), rosii: out.split('\n').filter((l) => l.startsWith(' RAU ')) }); }); }); } async function planteaza([nume, fisier, perechi, tinta]) { const t = copie(); try { const f = path.join(t, 'aere-verify-layer', fisier); let src = fs.readFileSync(f, 'utf8'); for (const [din, inl] of perechi) { if (src.split(din).length !== 2) return [false, ` STRICAT ${nume}: tiparul "${din.slice(0, 40)}" apare de ${src.split(din).length - 1} ori`]; src = src.replace(din, inl); } fs.writeFileSync(f, src); const r = await ruleaza(t); if (!r.rulat) return [false, ` STRICAT ${nume}: proba nu a ajuns la rezumat (cod ${r.cod})`]; if (r.rosii.some((l) => l.startsWith(' RAU ' + tinta))) return [true, ` ROSU cum trebuia ${nume} (proba '${tinta}' pica)`]; return [false, ` CONTROL CAZUT ${nume}: proba '${tinta}' a ramas verde (${r.rosii.length} rosii altundeva)`]; } finally { fs.rmSync(t, { recursive: true, force: true }); } } let rele = 0; const t0 = copie(); const m = await ruleaza(t0); fs.rmSync(t0, { recursive: true, force: true }); if (m.rulat && m.cod === 0 && !m.rosii.length) console.log(' OK martorul: copia neatinsa verde'); else { rele++; console.log(` STRICAT martorul nu e verde (cod ${m.cod}, ${m.rosii.length} rosii)`); } const rez = new Array(PLANTARI.length); let i = 0; await Promise.all(Array.from({ length: 4 }, async () => { while (i < PLANTARI.length) { const k = i++; rez[k] = await planteaza(PLANTARI[k]); } })); for (const [bun, linie] of rez) { console.log(linie); if (!bun) rele++; } console.log(rele ? `CONTROL NEGATIV: ${rele} probleme` : `DOVEDIT: martorul verde, ${PLANTARI.length} din ${PLANTARI.length} paznici scosi -> proba lor rosie`); process.exitCode = rele ? 1 : 0;