aere-quantum/payments/proba-risc.mjs

101 lines
16 KiB
JavaScript

#!/usr/bin/env node
// proba-risc.mjs: the laboratory of the risk step and the receipt (roadmap point 25), no chain: the risk verdicts under a declared
// policy (allowed, above the per-payment limit, above the window, refused counterparty, self-payment, no declared rate, reproducible
// hash, unmeasured signals named), and the receipt's verification on a synthetic payment (valid; then forged: amount, instruction,
// policy loosened under the same hash, inputs changed, a transaction that is not the payment). Three states per row.
// --control-negativ plants every PAZA marker out, one at a time, in copies of risc.mjs and chitanta.mjs, and requires the forged fixture
// it guards to be accepted (green-by-mistake), which proves the check is load-bearing.
// node proba-risc.mjs [--control-negativ] env: AERE_RISC_MODUL, AERE_CHITANTA_MODUL (the control sets its planted copies)
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const control = process.argv.includes('--control-negativ');
const incarca = async (risc, chit) => {
// chitanta.mjs imports ./risc.mjs by name: a planted chitanta copy must import the risc copy beside it, so the copy is rewritten
const R = await import(pathToFileURL(risc).href + '?t=' + Date.now() + Math.random());
const C = await import(pathToFileURL(chit).href + '?t=' + Date.now() + Math.random());
return { R, C };
};
const POLITICA = { v: 1, kind: 'aere-payment-risk-policy', id: 'lab-2026-10-02', currency: 'USD', perPaymentMaxUsd: '1.00', window: { seconds: 86400, maxUsd: '2.50', maxCount: 3 },
allowedAssets: [{ chainId: 28001, asset: 'native', decimals: 18 }],
refusedCounterparties: [{ address: '0x70099E62735500AA2F85B60C518551a57B202d54', reason: 'a contract with no path to withdraw what is sent to it (AereNotary on 28001)', source: 'contracts/deployments/notary-28001.json' }] };
const PAYER = '0x' + 'aa'.repeat(20), PAYEE = '0x' + 'bb'.repeat(20), RATE = { usdPerUnit: '0.5', source: 'declared by the operator for the laboratory', at: '2026-10-02T17:00:00.000Z' };
const inputs = (x = {}) => ({ chainId: 28001, payer: PAYER, payee: PAYEE, asset: 'native', amount: '400000000000000000', rate: RATE, identity: null, history: [], now: 1790960000, ...x });
const TX = '0x' + '11'.repeat(32), BLOC = '0x' + '22'.repeat(32);
const chitantaValida = (R, C) => { const v = R.judeca(POLITICA, inputs()); const r = C.construiesteChitanta({ chainId: 28001, txHash: TX, blockHash: BLOC, payer: PAYER, payee: PAYEE, asset: 'native', decimals: 18, amount: '400000000000000000', rate: RATE, verdict: v, policyId: POLITICA.id, createdAt: '2026-10-02T17:00:01.000Z' }); return { ...r, policy: POLITICA, inputs: inputs(), tx: { from: PAYER, to: PAYEE, value: '0x58d15e176280000', blockHash: BLOC }, receipt: { status: '0x1', blockHash: BLOC } }; };
const clona = (x) => JSON.parse(JSON.stringify(x));
const verif = (C, r) => C.verificaChitanta({ instruction: r.instruction, envelope: r.envelope }, { policy: r.policy, inputs: r.inputs, tx: r.tx, receipt: r.receipt });
export function fixturi({ R, C }) {
const F = []; const dec = (x, cond) => ({ verdict: cond ? x.decision : 'INVALID', detail: x.reasons.join('; ') || x.measured.amountUsd + ' USD' });
F.push({ nume: 'R1 0.4 units at 0.5 USD/unit = 0.20 USD: ALLOW; five unmeasured signals named', asteptat: 'ALLOW', r: () => { const v = R.judeca(POLITICA, inputs()); return dec(v, v.measured.amountUsd === '0.200000' && v.unmeasured.length === 5); } });
F.push({ nume: 'R2 the same policy and inputs twice: the same verdictHash; one input changed: another', asteptat: 'ALLOW', r: () => { const a = R.judeca(POLITICA, inputs()), b = R.judeca(POLITICA, inputs()), c = R.judeca(POLITICA, inputs({ now: 1790960001 })); return { verdict: a.verdictHash === b.verdictHash && a.verdictHash !== c.verdictHash ? 'ALLOW' : 'INVALID', detail: a.verdictHash.slice(0, 18) + '..' }; } });
F.push({ nume: 'R3 3 units = 1.50 USD: REFUSE above the per-payment limit', asteptat: 'REFUSE', paza: 'limita', r: () => dec(R.judeca(POLITICA, inputs({ amount: '3000000000000000000' })), true) });
F.push({ nume: 'R4 two earlier payments of 1.20 USD in the window + 0.20: REFUSE above the window limit (2.60 > 2.50)', asteptat: 'REFUSE', paza: 'fereastra', r: () => dec(R.judeca(POLITICA, inputs({ history: [{ at: 1790959000, amountUsd: '1.200000', payee: PAYEE }, { at: 1790959500, amountUsd: '1.200000', payee: PAYEE }] })), true) });
const H3 = [{ at: 1790959000, amountUsd: '0.100000' }, { at: 1790959100, amountUsd: '0.100000' }, { at: 1790959200, amountUsd: '0.100000' }];
F.push({ nume: 'R5 three earlier small payments in the window: REFUSE above the window count (4 > 3)', asteptat: 'REFUSE', paza: 'fereastra', r: () => dec(R.judeca(POLITICA, inputs({ history: H3 })), true) });
F.push({ nume: 'R5b the same with one of them outside the window: ALLOW (the window is a sliding 24 h)', asteptat: 'ALLOW', r: () => dec(R.judeca(POLITICA, inputs({ history: [...H3.slice(0, 2), { at: 1790959000 - 86400, amountUsd: '0.100000' }] })), true) });
F.push({ nume: 'R6 a refused counterparty (the notary contract, with its source): REFUSE', asteptat: 'REFUSE', paza: 'contrapartida', r: () => dec(R.judeca(POLITICA, inputs({ payee: POLITICA.refusedCounterparties[0].address })), true) });
F.push({ nume: 'R7 the payee is the payer: REFUSE', asteptat: 'REFUSE', paza: 'contrapartida', r: () => dec(R.judeca(POLITICA, inputs({ payee: PAYER })), true) });
F.push({ nume: 'R8 no declared rate: REFUSE', asteptat: 'REFUSE', paza: 'curs', r: () => dec(R.judeca(POLITICA, inputs({ rate: null })), true) });
F.push({ nume: 'R8b with no declared rate the USD value is left unmeasured and named so (never guessed)', asteptat: 'REFUSE', r: () => { const v = R.judeca(POLITICA, inputs({ rate: null })); return { verdict: v.measured.amountUsd === null && v.unmeasured.some((u) => /no declared rate/.test(u)) ? v.decision : 'INVALID', detail: v.unmeasured.find((u) => /rate/.test(u)) || '' }; } });
F.push({ nume: 'R9 an asset the policy does not allow: REFUSE', asteptat: 'REFUSE', paza: 'activ', r: () => dec(R.judeca(POLITICA, inputs({ asset: 'erc20:0x8215ba247a3574af8ebc36606eb437811e318fbd' })), true) });
F.push({ nume: 'R10 a policy with an unknown field is refused by definePolicy (nothing judged under a field the engine ignores)', asteptat: 'REFUSE', r: () => { try { R.definePolicy({ ...POLITICA, kycTier: 2 }); return { verdict: 'ALLOW', detail: 'accepted' }; } catch (e) { return { verdict: 'REFUSE', detail: e.message }; } } });
F.push({ nume: 'R11 a payer that is not an address: REFUSE, a verdict that names the form rule (until 2026-10-03 this threw instead)', asteptat: 'REFUSE', paza: 'forma', r: () => { const v = R.judeca(POLITICA, inputs({ payer: '0x1234' })); return dec(v, v.decision !== 'REFUSE' || v.rules.join() === 'forma'); } });
// the receipt
F.push({ nume: 'C1 a receipt on an ALLOW verdict verifies: envelope commits to the instruction, USD exact, policy under hash, verdict reproduced, transaction matches', asteptat: 'VALID', r: () => { const r = chitantaValida(R, C); const v = verif(C, r); return { verdict: v.verdict, detail: v.motive.join('; ') || r.instruction.amountUsd + ' USD, ' + r.envelope.statementHash.slice(0, 18) + '..' }; } });
F.push({ nume: 'C2 the same receipt without the chain facts: VALID but "chain not consulted"', asteptat: 'VALID', r: () => { const r = chitantaValida(R, C); const v = C.verificaChitanta({ instruction: r.instruction, envelope: r.envelope }, { policy: r.policy, inputs: r.inputs }); return { verdict: v.verdict === 'VALID' && v.chainChecked === false ? 'VALID' : 'INVALID', detail: v.motive.join('; ') }; } });
F.push({ nume: 'N1 the instruction\'s purpose text changed after the envelope (nothing else touched): INVALID, the envelope no longer commits to it', asteptat: 'INVALID', paza: 'legatura', r: () => { const r = chitantaValida(R, C); r.instruction.purpose = 'another purpose'; const v = verif(C, r); return { verdict: v.verdict, detail: v.motive.join('; ') }; } });
F.push({ nume: 'N1b the instruction\'s amount changed after the envelope: INVALID (linkage, USD arithmetic, verdict and chain all disagree)', asteptat: 'INVALID', r: () => { const r = chitantaValida(R, C); r.instruction.amount = '500000000000000000'; r.inputs.amount = r.instruction.amount; r.tx.value = '0x6f05b59d3b20000'; const v = verif(C, r); return { verdict: v.verdict, detail: v.motive.join('; ') }; } });
F.push({ nume: 'N2 amountUsd overstated on the instruction (and the envelope rebuilt to match): INVALID (not amount x rate)', asteptat: 'INVALID', paza: 'suma', r: () => { const r = chitantaValida(R, C); r.instruction.amountUsd = '0.300000'; r.envelope = C.construiesteChitanta({ chainId: 28001, txHash: TX, blockHash: BLOC, payer: PAYER, payee: PAYEE, asset: 'native', decimals: 18, amount: r.instruction.amount, rate: RATE, verdict: R.judeca(POLITICA, inputs()), policyId: POLITICA.id, createdAt: r.instruction.issuedAt }).envelope; r.envelope.statement.instructionHash = '0x' + require_sha(C, r.instruction); r.envelope.statementHash = '0x' + require_sha_stmt(r.envelope.statement); const v = verif(C, r); return { verdict: v.verdict, detail: v.motive.join('; ') }; } });
F.push({ nume: 'N3 the policy shown is LOOSER (per-payment 10 USD) but claims the receipt\'s policy hash: INVALID (policy changed under its hash; the verdict does not reproduce either)', asteptat: 'INVALID', r: () => { const r = chitantaValida(R, C); r.policy = { ...POLITICA, perPaymentMaxUsd: '10.00' }; const v = verif(C, r); return { verdict: v.verdict, detail: v.motive.join('; ') }; } });
F.push({ nume: 'N4 the inputs shown differ from the ones judged (a shorter history): INVALID (verdict not reproduced)', asteptat: 'INVALID', paza: 'risc', r: () => { const r = chitantaValida(R, C); r.inputs = inputs({ history: [{ at: 1790959900, amountUsd: '0.100000' }] }); const v = verif(C, r); return { verdict: v.verdict, detail: v.motive.join('; ') }; } });
F.push({ nume: 'N5 a receipt whose verdict was REFUSE cannot be built; one forged to say ALLOW over refusing inputs: INVALID', asteptat: 'INVALID', paza: 'risc', r: () => { const v = R.judeca(POLITICA, inputs({ amount: '3000000000000000000' })); let construit = true; try { C.construiesteChitanta({ chainId: 28001, txHash: TX, blockHash: BLOC, payer: PAYER, payee: PAYEE, asset: 'native', decimals: 18, amount: '3000000000000000000', rate: RATE, verdict: v, policyId: POLITICA.id }); } catch { construit = false; } const r = chitantaValida(R, C); r.instruction.amount = '3000000000000000000'; r.inputs = inputs({ amount: '3000000000000000000' }); r.instruction.risk.inputsHash = R.hashOf(r.inputs); r.instruction.amountUsd = '1.500000'; r.envelope = C.construiesteChitanta({ chainId: 28001, txHash: TX, blockHash: BLOC, payer: PAYER, payee: PAYEE, asset: 'native', decimals: 18, amount: '3000000000000000000', rate: RATE, verdict: { ...v, decision: 'ALLOW' }, policyId: POLITICA.id, createdAt: r.instruction.issuedAt }).envelope; r.instruction = JSON.parse(JSON.stringify(r.instruction)); r.instruction.risk.verdictHash = v.verdictHash; r.instruction.risk.decision = 'ALLOW'; r.instruction.txHash = TX; r.tx.value = '0x29a2241af62c0000'; const i2 = C.construiesteChitanta.length ? null : null; const inst = r.instruction; r.envelope.statement.instructionHash = '0x' + require_sha(C, inst); r.envelope.statementHash = '0x' + require_sha_stmt(r.envelope.statement); const vv = verif(C, r); return { verdict: !construit && vv.verdict === 'INVALID' ? 'INVALID' : 'VALID', detail: (construit ? 'a receipt WAS built on a REFUSE verdict; ' : 'refused to build; ') + vv.motive.join('; ') }; } });
F.push({ nume: 'N6 the transaction on chain goes to another payee: INVALID', asteptat: 'INVALID', paza: 'lant', r: () => { const r = chitantaValida(R, C); r.tx.to = '0x' + 'cc'.repeat(20); const v = verif(C, r); return { verdict: v.verdict, detail: v.motive.join('; ') }; } });
F.push({ nume: 'N7 the transaction on chain failed (status 0): INVALID', asteptat: 'INVALID', paza: 'lant', r: () => { const r = chitantaValida(R, C); r.receipt.status = '0x0'; const v = verif(C, r); return { verdict: v.verdict, detail: v.motive.join('; ') }; } });
return F;
}
import crypto from 'node:crypto';
const require_sha = (C, ins) => crypto.createHash('sha256').update(Buffer.from(JSON.stringify(sortKeys(ins)), 'utf8')).digest('hex');
const require_sha_stmt = (st) => crypto.createHash('sha256').update(Buffer.from(JSON.stringify(st), 'utf8')).digest('hex');
function sortKeys(x) { if (Array.isArray(x)) return x.map(sortKeys); if (x && typeof x === 'object') return Object.fromEntries(Object.keys(x).sort().map((k) => [k, sortKeys(x[k])])); return x; }
const RISC = process.env.AERE_RISC_MODUL || path.join(AICI, 'risc.mjs'), CHIT = process.env.AERE_CHITANTA_MODUL || path.join(AICI, 'chitanta.mjs');
if (!control) {
const M = await incarca(RISC, CHIT); let verzi = 0, rosii = 0, stricate = 0;
for (const f of fixturi(M)) {
let r; try { r = f.r(); } catch (e) { r = { verdict: 'STRICAT', detail: String(e.stack || e.message).split('\n').slice(0, 2).join(' ').slice(0, 160) }; }
const stare = r.verdict === 'STRICAT' ? 'STRICAT' : r.verdict === f.asteptat ? 'VERDE' : 'ROSU'; stare === 'VERDE' ? verzi++ : stare === 'ROSU' ? rosii++ : stricate++;
console.log(` [${stare.padEnd(7)}] ${f.nume}: ${r.verdict}${r.detail ? ' (' + String(r.detail).slice(0, 150) + ')' : ''}`);
}
console.log(`\npayments (risk + receipt): ${verzi} VERDE, ${rosii} ROSU, ${stricate} STRICAT`); process.exitCode = rosii || stricate ? 1 : 0;
} else {
const srcR = fs.readFileSync(RISC, 'utf8'), srcC = fs.readFileSync(CHIT, 'utf8'); let bune = 0;
// the policy-hash rule in chitanta.mjs carries no marker: it is a diagnostic (a clearer message) under the verdict reproduction, which
// already fails on any policy change (the policy hash is inside the verdict hash); the load-bearing check is PAZA:risc
const marcaje = [['forma', 'R'], ['activ', 'R'], ['contrapartida', 'R'], ['curs', 'R'], ['limita', 'R'], ['fereastra', 'R'], ['legatura', 'C'], ['suma', 'C'], ['risc', 'C'], ['lant', 'C']];
const toate = fixturi(await incarca(RISC, CHIT));
for (const [m, unde] of marcaje) {
const cR = path.join(AICI, `_plantat-risc-${process.pid}-${m}.mjs`), cC = path.join(AICI, `_plantat-chitanta-${process.pid}-${m}.mjs`);
const src = unde === 'R' ? srcR : srcC; const n = src.split(`/*PAZA:${m}*/`).length - 1; const tinte = toate.filter((f) => f.paza === m);
let stare, detaliu;
try {
if (n < 1 || !tinte.length) { stare = 'STRICAT'; detaliu = `marker ${n} times, ${tinte.length} fixtures`; }
else {
// the risc copy is always written (chitanta imports it by name); the planting goes in the file that holds the marker
fs.writeFileSync(cR, unde === 'R' ? srcR.split(`/*PAZA:${m}*/`).join("|| String(1) === '1' /*PLANTAT*/") : srcR);
fs.writeFileSync(cC, (unde === 'C' ? srcC.split(`/*PAZA:${m}*/`).join("|| String(1) === '1' /*PLANTAT*/") : srcC).replace("'./risc.mjs'", "'./" + path.basename(cR) + "'")); // the copy imports the risc copy beside it (written as a plain string: not an import of this file)
const fx = fixturi(await incarca(cR, cC)); const rez = [];
for (const t of tinte) { const f = fx.find((x) => x.nume === t.nume); const r = f.r(); rez.push(`${t.nume.split(' ')[0]}:${r.verdict}`); }
const trec = rez.every((x) => /:(VALID|ALLOW)$/.test(x)); stare = trec ? 'VERDE' : 'ROSU'; detaliu = (trec ? 'the forged fixture is accepted with the check planted out: ' : 'the planting did not change the verdict: ') + rez.join(' ');
}
} catch (e) { stare = 'STRICAT'; detaliu = String(e.message).slice(0, 160); } finally { fs.rmSync(cR, { force: true }); fs.rmSync(cC, { force: true }); }
if (stare === 'VERDE') bune++;
console.log(` [${stare.padEnd(7)}] PAZA:${m} in ${unde === 'R' ? 'risc.mjs' : 'chitanta.mjs'} (${n} marker${n === 1 ? '' : 's'}) ${detaliu}`);
}
const ramase = fs.readdirSync(AICI).filter((f) => f.startsWith('_plantat-'));
console.log(`CONTROL: ${bune} of ${marcaje.length} checks proven load-bearing${ramase.length ? '; copies left: ' + ramase.join(',') : ''}`); process.exitCode = bune === marcaje.length && !ramase.length ? 0 : 1;
}