aere-quantum/identity/control-negativ-travel-rule.mjs
Aere Network 8f5f0bd00d verify-layer: verify-consistency names the signer of each head (or unsigned); identity: Travel Rule requires a replay store
- verify-consistency without --signer now says whether each head is signed and by which key, and that no expected signer was
  checked (verify-inclusion already did; the README said the output does)
- openMessage opens nothing without a replay store (seen: an object with has/add that keeps the ids for at least maxAgeS);
  before, the same signed and sealed message could be opened any number of times when no store was given

Tests: verify-layer tree 20/20, negative control 14/14; sidecar 44/44, 9/9; travel rule 19/19, negative control 19/19.
2026-09-30 10:31:23 +03:00

77 lines
7.6 KiB
JavaScript

// 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';
const PLANTARI = [
// [nume, [[tipar, inlocuire], ...], proba (inceputul numelui ei)]
['legarea KEM nesemnata de VASP-ul ei primita', [["if (!verifyText('kem-binding', canonical(S), binding.signature, S.vasp.keys)) motive.push(", 'if (false) motive.push(']], 'ATAC: legarea KEM semnata de alt VASP'],
['credentialul altei identitati primit', [['if (!holder || holder.id !== id) motive.push(', 'if (false) motive.push(']], 'ATAC: legarea KEM a lui C cu prezentarea'],
['un credential fara vasp:true primit', [['if (v.valid && (!v.claims || v.claims.vasp !== true)) motive.push(', 'if (false) motive.push(']], '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'],
['starea nejudecata a VASP-ului primita', [['if (!(stare && stare.pass === true)) motive.push(', 'if (false) motive.push(']], 'ATAC: un VASP revocat'],
['legarea expirata primita', [["if (!(timp(S.validFrom, 'validFrom') <= acum && acum <= timp(S.validUntil, 'validUntil'))) motive.push(", 'if (false) motive.push(']], 'ATAC: prezentarea beneficiarului facuta pentru alt initiator'],
['semnatura mesajului nu se mai verifica', [["if (!verifyText('travel-rule', canonical(semnat), m.signature, m.from.keys)) motive.push(", 'if (false) motive.push(']], 'ATAC: mesaj semnat de un strain'],
['destinatarul nu se mai compara', [['if (m.to.id !== me) motive.push(', 'if (false) motive.push(']], 'ATAC: alt VASP (C)'],
['cheile KEM ale destinatarului nu se mai compara', [['if (canonical(m.to.kem) !== canonical(kem.public)) motive.push(', 'if (false) motive.push(']], 'ATAC: alt VASP (C)'],
['prospetimea nu se mai cere', [["if (Math.abs(acum - timp(m.createdAt, 'createdAt')) > maxAgeS * 1000) motive.push(", 'if (false) motive.push(']], 'ATAC: acelasi mesaj primit a doua oara'],
['reluarea nu se mai opreste', [[' else if (seen.has(m.id)) motive.push(', ' else if (false) motive.push(']], '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)', [[" if (!seen || typeof seen.has !== 'function' || typeof seen.add !== 'function') motive.push(", " if (false) motive.push("], [" else if (seen.has(m.id)) motive.push(", " else if (seen && seen.has && seen.has(m.id)) motive.push("], [' seen.add(m.id);', ' if (seen && seen.add) seen.add(m.id);']], 'B-26'],
['initiatorul nu mai trebuie dovedit VASP', [["motive.push(...v.motive.map((x) => 'originator VASP: ' + x));", '']], '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', [['if (S.from.id !== beneficiaryId || idOf(S.from.keys) !== S.from.id) motive.push(', 'if (false) motive.push(']], 'ATAC: confirmare semnata de C'],
['confirmarea altui mesaj primita', [["if (S.messageId !== message.id || S.messageHash !== sha(Buffer.from(canonical(semnat), 'utf8'))) motive.push(", 'if (false) motive.push(']], '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;