// Controlul negativ al Travel Rule (travel-rule.mjs, proba-travel-rule.mjs): fiecare paznic scos intr-o COPIE trebuie sa inroseasca // proba NUMITA, cu proba chiar rulata; pe copia neatinsa, verde. O plantare poate avea mai multe inlocuiri (un paznic dublat, de pilda // antetul legat si ca AAD si prin derivarea cheii, se scoate intreg). 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-travel-rule.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 DEV_VERIFY = path.resolve(AICI, '..', 'aere-proof-protocol', 'verify.mjs'); const VERIFY = process.env.AERE_VERIFY_PROOF || (fs.existsSync(DEV_VERIFY) ? DEV_VERIFY : ''); const T = 'travel-rule.mjs'; // 2026-10-18: tiparele sunt pe forma cu reguli numite (fiecare verificare e `C.ok('', /*PAZA:*/, ...)`; // un paznic se scoate facand conditia buna `true`, cu marcajul pastrat) const PLANTARI = [ // [nume, [[tipar, inlocuire], ...], proba (inceputul numelui ei)] ['legarea KEM nesemnata de VASP-ul ei primita', [["C.ok('binding-signature', verifyText('kem-binding', canonical(S), binding.signature, S.vasp.keys) /*PAZA:binding-signature*/,", "C.ok('binding-signature', true /*PAZA:binding-signature*/,"]], 'ATAC: legarea KEM semnata de alt VASP'], ['credentialul altei identitati primit', [["ok('vasp-identity', !!(holder && holder.id === id) /*PAZA:vasp-identity*/,", "ok('vasp-identity', true /*PAZA:vasp-identity*/,"]], 'ATAC: legarea KEM a lui C cu prezentarea'], ['un credential fara vasp:true primit', [["ok('vasp-claim', (!v.valid || !!(v.claims && v.claims.vasp === true)) /*PAZA:vasp-claim*/,", "ok('vasp-claim', true /*PAZA:vasp-claim*/,"]], 'ATAC: credential de VASP de la un registru'], ['registrele de incredere nu mai ajung la verificare', [['trustedIssuers: registries,', 'trustedIssuers: null,']], 'ATAC: credential de VASP de la un registru'], // 2026-10-18: forma de dinainte, in care publicul si nonce-ul lasate nejudecate de verificatorul de identitate nu refuzau prezentarea ['publicul si nonce-ul nejudecate iar acceptate', [["const nejudecate = v.rows.filter((r) => r.pass === null && (r.unjudged === 'audience' || r.unjudged === 'nonce'));", 'const nejudecate = [];']], 'ATAC: acceptBeneficiary fara originatorId'], ['starea nejudecata a VASP-ului primita', [["ok('vasp-status', !!(stare && stare.pass === true) /*PAZA:vasp-status*/,", "ok('vasp-status', true /*PAZA:vasp-status*/,"]], 'ATAC: un VASP revocat'], ['legarea expirata primita', [["C.ok('binding-window', (timp(S.validFrom, 'validFrom') <= acum && acum <= timp(S.validUntil, 'validUntil')) /*PAZA:binding-window*/,", "C.ok('binding-window', true /*PAZA:binding-window*/,"]], 'ATAC: prezentarea beneficiarului facuta pentru alt initiator'], ['semnatura mesajului nu se mai verifica', [["C.ok('message-signature', verifyText('travel-rule', canonical(semnat), m.signature, m.from.keys) /*PAZA:message-signature*/,", "C.ok('message-signature', true /*PAZA:message-signature*/,"]], 'ATAC: mesaj semnat de un strain'], ['destinatarul nu se mai compara', [["C.ok('message-recipient', m.to.id === me /*PAZA:message-recipient*/,", "C.ok('message-recipient', true /*PAZA:message-recipient*/,"]], 'ATAC: alt VASP (C)'], ['cheile KEM ale destinatarului nu se mai compara', [["C.ok('message-kem-keys', canonical(m.to.kem) === canonical(kem.public) /*PAZA:message-kem-keys*/,", "C.ok('message-kem-keys', true /*PAZA:message-kem-keys*/,"]], 'ATAC: alt VASP (C)'], ['prospetimea nu se mai cere', [["C.ok('message-freshness', (Number.isFinite(acum) && Math.abs(acum - timp(m.createdAt, 'createdAt')) <= maxAgeS * 1000) /*PAZA:message-freshness*/,", "C.ok('message-freshness', true /*PAZA:message-freshness*/,"]], 'ATAC: acelasi mesaj primit a doua oara'], ['reluarea nu se mai opreste', [["C.ok('replay', !seen.has(m.id) /*PAZA:replay*/,", "C.ok('replay', true /*PAZA:replay*/,"]], 'ATAC: acelasi mesaj primit a doua oara'], // B-26: forma publicata in f75c334 (seen optional: fara el, orice reluare trecea) ['magazia de reluare iar optionala (B-26)', [["C.ok('replay-store', (!!seen && typeof seen.has === 'function' && typeof seen.add === 'function') /*PAZA:replay-store*/,", "C.ok('replay-store', true /*PAZA:replay-store*/,"], ["C.ok('replay', !seen.has(m.id) /*PAZA:replay*/,", "C.ok('replay', !(seen && seen.has && seen.has(m.id)) /*PAZA:replay*/,"], [' seen.add(m.id);', ' if (seen && seen.add) seen.add(m.id);']], 'B-26'], ['initiatorul nu mai trebuie dovedit VASP', [["const v = eVasp(m.fromPresentation, { registries, audience: me, nonce: m.id, id: m.from.id, now, statusLists, maxAgeS }, C.ok, 'originator VASP: ');", 'const v = { claims: null };']], 'ATAC: initiatorul nedovedit ca VASP'], ['antetul nu mai e legat de cifru (nici AAD, nici derivarea)', [["const c = crypto.createCipheriv('aes-256-gcm', d.key, d.iv); c.setAAD(aad);", "const c = crypto.createCipheriv('aes-256-gcm', d.key, d.iv);"], ['dc.setAAD(aad); dc.setAuthTag', 'dc.setAuthTag'], ["crypto.createHash('sha256').update(transcript).digest()", 'Buffer.alloc(32)']], 'ATAC: suma din antet schimbata'], ['numai secretul X25519 intra in cheie', [['const ikm = Buffer.concat([ssK, ssX]);', 'const ikm = Buffer.concat([ssX]);']], 'KEM hibrid'], ['confirmarea altui VASP primita', [["C.ok('receipt-from', (S.from.id === beneficiaryId && idOf(S.from.keys) === S.from.id) /*PAZA:receipt-from*/,", "C.ok('receipt-from', true /*PAZA:receipt-from*/,"]], 'ATAC: confirmare semnata de C'], ['confirmarea altui mesaj primita', [["C.ok('receipt-message', (S.messageId === message.id && S.messageHash === sha(Buffer.from(canonical(semnat), 'utf8'))) /*PAZA:receipt-message*/,", "C.ok('receipt-message', true /*PAZA:receipt-message*/,"]], 'ATAC: confirmare semnata de C'], ['id-ul initiatorului scris in inregistrare', [["const subject = 'transfer:' + sha(", 'const subject = message.from.id + sha(']], 'inregistrarea (plic AIP-23 compliance)'], ['un fisier de chei KEM cu partea publica a altcuiva primit', [['if (canonical(pub) !== canonical(j.public)) throw', 'if (false) throw']], 'cheile KEM'], ]; const FISIERE = ['identity.mjs', T, 'proba-travel-rule.mjs']; function copie() { const t = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-tr-ctl-')); fs.mkdirSync(path.join(t, 'aere-identity')); fs.mkdirSync(path.join(t, 'proof-kinds')); for (const f of FISIERE) fs.copyFileSync(path.join(AICI, f), path.join(t, 'aere-identity', f)); fs.copyFileSync(path.join(AICI, '..', 'proof-kinds', 'proof-kinds.mjs'), path.join(t, 'proof-kinds', 'proof-kinds.mjs')); return t; } function ruleaza(t) { const env = { ...process.env }; if (VERIFY) env.AERE_VERIFY_PROOF = VERIFY; else delete env.AERE_VERIFY_PROOF; return new Promise((resolve) => { const c = spawn(process.execPath, [path.join(t, 'aere-identity', 'proba-travel-rule.mjs')], { env }); 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-travel-rule: \d+\/\d+/.test(out), rosii: out.split('\n').filter((l) => l.startsWith(' RAU ')) }); }); }); } async function planteaza([nume, perechi, tinta]) { const t = copie(); try { const f = path.join(t, 'aere-identity', T); 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.rosii.length && (m.cod === 0 || (m.cod === 2 && !VERIFY))) console.log(' OK martorul: copia neatinsa verde' + (m.cod === 2 ? ' (plicul AIP-23 NEMASURAT: fara verificator)' : '')); 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;