102 lines
7.6 KiB
JavaScript
102 lines
7.6 KiB
JavaScript
// Proba B3 milestone 3, PE LANT: jurnalul de audit al unei desfasurari -> capul lui atestat -> notarizat pe AereNotary de pe
|
|
// testnetul public 28001 -> verify-proof ii da finalitate POST-CUANTICA (ancora certificata -> radacina de stare -> dovada Merkle a
|
|
// lui firstSeen). Cu cheia de DEZVOLTATOR a testnetului (valoare de test, niciodata tiparita), niciodata o cheie de mainnet.
|
|
// node proba-notarizare-testnet.mjs --key-file <CHEIE-DEZVOLTATOR-TESTNET.key>
|
|
// Cazuri, fiecare cu perechea lui:
|
|
// A inainte de notarizare, verify-proof pe cap: finalitatea NU e post-cuantica, absenta e DOVEDITA ("not notarized")
|
|
// B notarize-head: tranzactie reusita, firstSeen > 0, plicul iesit declara notarizarea
|
|
// C verify-proof pe plicul notarizat: finality PASSED post-quantum, cu timpul = firstSeen (asteapta ancora certificata de dupa)
|
|
// D CONTROL: o intrare din jurnal manipulata -> verify-log rupt la seq-ul ei, attest-head refuza
|
|
// E CONTROL: capul altui jurnal (o intrare in plus) -> verify-proof: NEnotarizat, nu post-cuantic
|
|
// F CONTROL: plic cu statement schimbat si statementHash vechi -> notarize-head REFUZA, fara tranzactie
|
|
// G CONTROL: RPC-ul mainnetului (2800) fara AERE_CONFIRM_MAINNET=yes -> REFUZ inainte de cheie, cod 3
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
|
const SIDECAR = path.join(AICI, 'sidecar.mjs');
|
|
const VERIFY = process.env.AERE_VERIFY_PROOF ? path.resolve(process.env.AERE_VERIFY_PROOF) : path.resolve(AICI, '..', '..', 'verificator-falcon', 'verify-proof.mjs');
|
|
const RPC = 'https://testnet-rpc.aere.network';
|
|
const arg = (n) => { const i = process.argv.indexOf('--' + n); return i > 0 ? process.argv[i + 1] : null; };
|
|
const KEY = arg('key-file');
|
|
if (!KEY || !fs.existsSync(KEY)) { console.error('cer --key-file <cheia de dezvoltator a testnetului>'); process.exitCode = 2; }
|
|
const T = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-b3-notar-'));
|
|
let ok = 0, rau = 0; const linii = [];
|
|
const cere = (c, ce) => { linii.push((c ? 'OK ' : 'RAU ') + ce); c ? ok++ : rau++; };
|
|
const taie = (s) => String(s ?? '').replace(/(0x)?[0-9a-fA-F]{40,}/g, (m) => (m.length === 66 ? m.slice(0, 12) + '..' : '<hex>')).slice(0, 220);
|
|
function run(script, args, env = {}) {
|
|
try { return { cod: 0, out: execFileSync(process.execPath, [script, ...args], { encoding: 'utf8', env: { ...process.env, ...env }, stdio: ['ignore', 'pipe', 'pipe'] }) }; }
|
|
catch (e) { return { cod: e.status ?? 2, out: String(e.stdout || '') + String(e.stderr || '') }; }
|
|
}
|
|
const verifica = (f) => { const r = run(VERIFY, [f, '--rpc', RPC, '--chain', '28001', '--json']); try { return JSON.parse(r.out); } catch { return { verdict: '?', levels: [], brut: r.out }; } };
|
|
const fin = (v) => (v.levels || []).find((l) => l.level === 'finality') || {};
|
|
const dormi = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
if (KEY && fs.existsSync(KEY)) try {
|
|
const log = path.join(T, 'audit.log');
|
|
const creazaJurnal = (logf, n) => {
|
|
for (let i = 1; i <= n; i++) {
|
|
const f = path.join(T, `artefact-${i}.bin`); fs.writeFileSync(f, `desfasurarea ${i} a serviciului de proba, ${T}`);
|
|
const r = run(SIDECAR, ['record', '--kind', 'deployment', '--name', 'serviciu-proba', '--version', `1.0.${i}`, '--content-file', f, '--log', logf, '--host', 'proba-b3']);
|
|
if (r.cod !== 0) throw new Error('record a cazut: ' + taie(r.out));
|
|
}
|
|
};
|
|
creazaJurnal(log, 3);
|
|
cere(run(SIDECAR, ['verify-log', '--log', log]).cod === 0, 'jurnalul de 3 desfasurari e intreg');
|
|
const head = path.join(T, 'cap.json');
|
|
cere(run(SIDECAR, ['attest-head', '--log', log, '--out', head, '--host', 'proba-b3']).cod === 0 && fs.existsSync(head), 'capul lantului atestat (aere-audit-head)');
|
|
|
|
// A: absenta DOVEDITA inainte
|
|
const vA = verifica(head); const fA = fin(vA);
|
|
cere(fA.state !== 'PASSED' && /not notarized|ABSENT|no record/i.test(String(fA.detail) + fA.state), `A inainte de notarizare: finality ${fA.state} (${taie(fA.detail)})`);
|
|
|
|
// B: notarizarea
|
|
const notar = path.join(T, 'cap-notarizat.json');
|
|
const rB = run(SIDECAR, ['notarize-head', '--head', head, '--rpc', RPC, '--key-file', KEY, '--out', notar]);
|
|
const pB = fs.existsSync(notar) ? JSON.parse(fs.readFileSync(notar, 'utf8')) : null;
|
|
cere(rB.cod === 0 && pB && pB.notarization && pB.notarization.firstSeen > 0 && pB.notarization.chainId === 28001, `B notarize-head: ${taie(rB.out.trim().split('\n').pop())}`);
|
|
|
|
// C: finalitatea post-cuantica apare dupa urmatoarea ancora certificata (asteptare marginita, 5 minute)
|
|
let vC = null, fC = {}, incercari = 0;
|
|
for (const t0 = Date.now(); Date.now() - t0 < 300000; incercari++) {
|
|
vC = verifica(notar); fC = fin(vC);
|
|
if (fC.state === 'PASSED' && /post-quantum/.test(fC.detail || '')) break;
|
|
await dormi(20000);
|
|
}
|
|
const timp = pB && pB.notarization ? String(pB.notarization.firstSeen) : 'X';
|
|
cere(vC.verdict === 'VALID' && fC.state === 'PASSED' && /post-quantum/.test(fC.detail || '') && String(fC.detail).includes(timp), `C verify-proof: finality ${fC.state} post-quantum cu firstSeen ${timp} (dupa ${incercari} reincercari): ${taie(fC.detail)}`);
|
|
|
|
// D: jurnal manipulat
|
|
const logD = path.join(T, 'audit-manipulat.log'); fs.copyFileSync(log, logD);
|
|
const ld = fs.readFileSync(logD, 'utf8').split('\n'); const o = JSON.parse(ld[1]); o.proof.statement.name = 'serviciu-strain'; ld[1] = JSON.stringify(o); fs.writeFileSync(logD, ld.join('\n'));
|
|
const rD = run(SIDECAR, ['verify-log', '--log', logD]); const rD2 = run(SIDECAR, ['attest-head', '--log', logD, '--out', path.join(T, 'x.json')]);
|
|
cere(rD.cod === 1 && /seq 1/.test(rD.out) && rD2.cod === 1, `D CONTROL: intrare manipulata -> ${taie(rD.out.trim())}; attest-head refuza`);
|
|
|
|
// E: capul altui jurnal
|
|
const logE = path.join(T, 'audit-altul.log'); fs.copyFileSync(log, logE); creazaJurnal(logE, 1);
|
|
const headE = path.join(T, 'cap-altul.json'); run(SIDECAR, ['attest-head', '--log', logE, '--out', headE, '--host', 'proba-b3']);
|
|
const fE = fin(verifica(headE));
|
|
cere(fE.state !== 'PASSED', `E CONTROL: capul unui jurnal cu o intrare in plus NU e notarizat: finality ${fE.state} (${taie(fE.detail)})`);
|
|
|
|
// F: plic manipulat
|
|
const pF = JSON.parse(fs.readFileSync(head, 'utf8')); pF.statement.count = pF.statement.count + 1; const headF = path.join(T, 'cap-manipulat.json'); fs.writeFileSync(headF, JSON.stringify(pF));
|
|
const rF = run(SIDECAR, ['notarize-head', '--head', headF, '--rpc', RPC, '--key-file', KEY]);
|
|
cere(rF.cod === 1 && /modified attestation/.test(rF.out) && !/notarized on chain/.test(rF.out), `F CONTROL: statement schimbat -> notarize-head refuza fara tranzactie (${taie(rF.out.trim())})`);
|
|
|
|
// G: garda de mainnet
|
|
const rG = run(SIDECAR, ['notarize-head', '--head', head, '--rpc', 'https://rpc.aere.network', '--key-file', KEY], { AERE_CONFIRM_MAINNET: '' });
|
|
cere(rG.cod === 3 && /2800/.test(rG.out), `G CONTROL: RPC de mainnet fara DA -> cod ${rG.cod}, refuzat (${taie(rG.out.trim())})`);
|
|
|
|
// pastreaza dovada: capul notarizat si jurnalul lui, fara nimic secret
|
|
const dov = path.join(AICI, 'dovezi-notarizare-testnet'); fs.mkdirSync(dov, { recursive: true });
|
|
if (pB) fs.writeFileSync(path.join(dov, 'cap-notarizat-28001.json'), JSON.stringify(pB, null, 1) + '\n');
|
|
fs.copyFileSync(log, path.join(dov, 'audit.log'));
|
|
} catch (e) { linii.push('RAU proba nu a putut rula: ' + taie(e.message)); rau++; }
|
|
finally { try { fs.rmSync(T, { recursive: true, force: true }); } catch {} }
|
|
for (const l of linii) console.log(l);
|
|
console.log(`B3 notarizare pe testnet: ${ok}/${ok + rau} cum trebuia`);
|
|
if (!process.exitCode) process.exitCode = rau ? 1 : 0;
|