// Proba registrului de agent (agent-ledger.mjs): identitate PQ + registru semnat, cu limita impusa pe sesiune si verificator care nu se // increde in registru. Fiecare afirmatie are perechea ei negativa. Offline, deterministic (ceas injectat). Numai Node 24 (ML-DSA). // Forma 2 (2026-09-29, B-17): politica primita de verificator se recalculeaza la hash, momentul intrarilor e marginit si de jos (la // scriere de ceasul registrului; la verificare de ancore si notBefore, cand exista un martor), si fiecare atac e reprodus inainte. import crypto from 'node:crypto'; import { definePolicy } from './agent-policy.mjs'; import { newAgentIdentity, agentIdFromKey, openLedger, resumeLedger, verifyLedger, findEquivocation, verifyEquivocation, sessionId, canonical } from './agent-ledger.mjs'; let ok = 0, rau = 0; const linii = []; const cer = (c, ce) => { linii.push((c ? 'OK ' : 'RAU ') + ce); c ? ok++ : rau++; }; const arunca = (fn) => { try { fn(); return null; } catch (e) { return e.message; } }; // identitate legata de cheie const id = newAgentIdentity(); cer(id.agentId.startsWith('aere-agent:') && id.agentId === agentIdFromKey(id.publicKey), '1. agentId derivat din cheia publica'); const id2 = newAgentIdentity(); cer(id.agentId !== id2.agentId, '1. CONTROL: alta cheie -> alt agentId'); // politica: 100 pe fereastra de 3600 s, un destinatar, o unealta const { policy, policyHash } = definePolicy({ agentId: id.agentId, spend: { amount: '100', windowSeconds: 3600 }, recipients: ['0xbbbb'], tools: ['retrieval'] }); // un ceas injectat: sesiunea incepe la t=1000 let t = 1000; const now = () => t; const L = openLedger({ identity: id, policy, policyHash, now }); cer(L.session === sessionId(id.agentId, policyHash) && /^[0-9a-f]{32}$/.test(L.session), `1. sesiunea registrului e derivata din agent si politica (${L.session.slice(0, 8)}...)`); cer(openLedger({ identity: id, policy, policyHash, now }).session === L.session, '1. un al doilea registru al aceluiasi agent sub aceeasi politica are ACEEASI sesiune (e o ramura, nu alt registru)'); cer(sessionId(id.agentId, definePolicy({ agentId: id.agentId, tools: ['x'] }).policyHash) !== L.session, '1. CONTROL: sub alta politica, alta sesiune'); // limita impusa PE SESIUNE: trei plati de 40 in aceeasi fereastra - a treia depaseste 100 const a = L.record({ kind: 'payment', to: '0xbbbb', amount: '40' }); const b = L.record({ kind: 'payment', to: '0xbbbb', amount: '40' }); const c = L.record({ kind: 'payment', to: '0xbbbb', amount: '40' }); cer(a.allowed && b.allowed && !c.allowed, `2. limita pe sesiune: 40+40 permise, al treilea 40 REFUZAT (${c.reason})`); cer(/over the limit/.test(c.reason), '2. motivul refuzului e depasirea limitei'); // fereastra se roteste: dupa 3600 s cheltuiala veche nu mai conteaza t = 1000 + 3601; const d = L.record({ kind: 'payment', to: '0xbbbb', amount: '90' }); cer(d.allowed, '3. dupa fereastra, o plata noua de 90 e permisa (cheltuiala veche a expirat)'); // destinatar nepermis, unealta nepermisa, unealta permisa cer(!L.record({ kind: 'payment', to: '0xcccc', amount: '1' }).allowed, '4. CONTROL: destinatar nepermis -> refuzat'); cer(!L.record({ kind: 'tool', tool: 'shell' }).allowed, '4. CONTROL: unealta nepermisa -> refuzat'); cer(L.record({ kind: 'tool', tool: 'retrieval' }).allowed, '4. unealta permisa -> allowed'); // provenienta legata (proof-of-ai) const provenance = { model: { name: 'example-model', version: '1.0' }, prompt: 'summarize', tool: 'retrieval' }; const withProv = L.record({ kind: 'tool', tool: 'retrieval' }, { provenance }); cer(withProv.entry.body.provenance && withProv.entry.body.provenance.length === 64, '5. provenienta se leaga prin hash in intrare'); // --- verificatorul, care NU se increde in registru --- const reg = L.export(); const v = verifyLedger(reg, { policy, policyHash, maxTime: t + 300 }); cer(v.ok && v.seq === reg.entries.length, `6. registrul intreg se verifica (${v.seq} intrari, plati permise ${v.allowedPayments}, cheltuit ${v.spent})`); cer(v.spent === '170', `6. cheltuiala permisa re-derivata = 40+40+90 = 170 (${v.spent})`); cer(/none/.test(v.time.lowerBound), `6. fara martor, rezultatul spune ca timpul nu e marginit de jos (${v.time.lowerBound})`); cer(verifyLedger(reg, { policy, maxTime: t + 300 }).ok, '6. fara hash fixat, verificatorul il recalculeaza din politica si il compara cu al registrului'); // CONTROL: suma unei intrari schimbata -> semnatura pica const t1 = JSON.parse(JSON.stringify(reg)); t1.entries[0].body.action.amount = '39'; const vt1 = verifyLedger(t1, { policy, policyHash, maxTime: t + 300 }); cer(!vt1.ok && vt1.seq === 0 && /signature/.test(vt1.error), `7. CONTROL: suma schimbata la seq 0 -> prinsa (${vt1.error})`); // re-semnarea de catre adversar: ARE cheia agentului, deci isi poate rescrie si re-lega registrul function resemneaza(r0, i, schimba) { const r = JSON.parse(JSON.stringify(r0)); schimba(r); let prev = i ? r.entries[i - 1].hash : '0'.repeat(64); for (let k = i; k < r.entries.length; k++) { const e = r.entries[k]; e.seq = k; e.body.seq = k; e.prev = prev; e.signature = crypto.sign(null, Buffer.from(`${k}|${prev}|${canonical(e.body)}`, 'utf8'), id.privateKey).toString('base64'); e.hash = crypto.createHash('sha256').update(`${k}|${prev}|${canonical(e.body)}|${e.signature}`).digest('hex'); prev = e.hash; } return r; } // CONTROL: un "allowed" mincinos peste refuz, RE-SEMNAT cu cheia agentului -> verificatorul re-ruleaza politica si prinde decizia falsa const iRef = reg.entries.findIndex((e) => e.body.action.kind === 'payment' && !e.body.decision.allowed); const t2 = resemneaza(reg, iRef, (r) => { r.entries[iRef].body.decision = { allowed: true, reason: 'under the limit' }; }); const vt2 = verifyLedger(t2, { policy, policyHash, maxTime: t + 300 }); cer(!vt2.ok && vt2.seq === iRef && /false decision/.test(vt2.error), `8. CONTROL: "allowed" mincinos peste refuz, re-semnat corect -> prins (${vt2.error})`); // CONTROL: o intrare stearsa -> lantul nu mai leaga const t3 = JSON.parse(JSON.stringify(reg)); t3.entries.splice(1, 1); t3.entries.forEach((e, i) => { e.seq = i; }); const vt3 = verifyLedger(t3, { policy, policyHash, maxTime: t + 300 }); cer(!vt3.ok && /link|header/.test(vt3.error), `9. CONTROL: intrare stearsa -> prinsa (${vt3.error})`); // CONTROL: registrul altui agent judecat cu politica noastra const idX = newAgentIdentity(); const { policy: polX, policyHash: phX } = definePolicy({ agentId: idX.agentId, spend: { amount: '100', windowSeconds: 3600 }, recipients: ['0xbbbb'] }); const LX = openLedger({ identity: idX, policy: polX, policyHash: phX, now: () => 1000 }); LX.record({ kind: 'payment', to: '0xbbbb', amount: '10' }); const regX = LX.export(); const vX = verifyLedger(regX, { policy, policyHash, maxTime: 2000 }); cer(!vX.ok && /another policy|another agent|agentId/.test(vX.error), `10. CONTROL: registru al altui agent judecat cu politica noastra -> refuzat (${vX.error})`); cer(verifyLedger(regX, { policy: polX, policyHash: phX, maxTime: 2000 }).ok, '10. registrul altui agent cu politica LUI se verifica (metoda e buna)'); // CONTROL: cheia publica inlocuita in registru (identitate falsa) -> agentId nu mai deriva din ea const t4 = JSON.parse(JSON.stringify(reg)); t4.publicKeyPem = idX.publicKey.export({ type: 'spki', format: 'pem' }); const vt4 = verifyLedger(t4, { policy, policyHash, maxTime: t + 300 }); cer(!vt4.ok && /false identity/.test(vt4.error), `11. CONTROL: cheia publica inlocuita -> identitate falsa prinsa (${vt4.error})`); // --- atacurile gasite de revizuirea adversariala (2026-09-27), fiecare reprodus inainte de reparatie --- { const idA = newAgentIdentity(); const { policy: pA, policyHash: phA } = definePolicy({ agentId: idA.agentId, spend: { amount: '100', windowSeconds: 3600 }, recipients: ['0xbbbb'] }); // 12: suma negativa urmata de una uriasa (inainte: amandoua allowed, verificatorul "ok") const LA = openLedger({ identity: idA, policy: pA, policyHash: phA, now: () => 1000 }); const neg = LA.record({ kind: 'payment', to: '0xbbbb', amount: '-1000000' }); const uri = LA.record({ kind: 'payment', to: '0xbbbb', amount: '1000000' }); cer(!neg.allowed && /invalid amount/.test(neg.reason) && !uri.allowed, `12. ATAC: plata negativa REFUZATA (${neg.reason.slice(0, 60)}), iar 1.000.000 dupa ea refuzata pe limita`); cer(!LA.record({ kind: 'payment', to: '0xbbbb', amount: '0' }).allowed && !LA.record({ kind: 'payment', to: '0xbbbb', amount: '1.5' }).allowed, '12. suma zero si suma zecimala refuzate'); cer(LA.record({ kind: 'payment', to: '0xbbbb', amount: '60' }).allowed, '12. CONTROL: o plata valida sub limita tot trece (reparatia nu blocheaza tot)'); // 13: timpul inapoi si timpul in viitorul ceasului registrului -> refuzate la scriere cer(/backwards/.test(arunca(() => LA.record({ kind: 'payment', to: '0xbbbb', amount: '1' }, { at: 999 })) || ''), '13. ATAC: timp inapoi refuzat la scriere'); cer(/ahead of the ledger clock/.test(arunca(() => LA.record({ kind: 'payment', to: '0xbbbb', amount: '100' }, { at: 1000 + 3601 })) || ''), '13. ATAC: salt de fereastra (at = acum + 3601) refuzat la scriere'); // 14: un registru "din viitor" produs cu un ceas FALSIFICAT al agentului: se scrie, dar verificatorul, pe ceasul LUI, il refuza let tFals = 1000; const LF = openLedger({ identity: idA, policy: pA, policyHash: phA, now: () => tFals }); let permise = 0; for (let i = 0; i < 10; i++) { tFals = 1000 + i * 3601; if (LF.record({ kind: 'payment', to: '0xbbbb', amount: '100' }).allowed) permise++; } const vF = verifyLedger(LF.export(), { policy: pA, policyHash: phA, maxTime: 1000 + 300 }); cer(permise === 10 && !vF.ok && /future/.test(vF.error), `14. ATAC: 10 x 100 "in 10 ore" cu ceasul agentului falsificat -> verificatorul (ceasul lui) refuza la seq ${vF.seq}`); const vF2 = verifyLedger(LF.export(), { policy: pA, policyHash: phA, maxTime: 1000 + 10 * 3601 }); cer(vF2.ok, '14. CONTROL: acelasi registru, verificat la un moment care chiar e dupa cele 10 ore, e valid (timpul real a trecut)'); } // --- revizuirea adversariala din 2026-09-29 (B-17), fiecare atac reprodus pe forma 1 inainte de reparatie --- { const idB = newAgentIdentity(); const real = definePolicy({ agentId: idB.agentId, spend: { amount: '100', windowSeconds: 3600 }, recipients: ['0xbbbb'] }); const lax = definePolicy({ agentId: idB.agentId, spend: { amount: '1000000', windowSeconds: 3600 }, recipients: ['0xbbbb'] }); // 15: politica LAXA sub hash-ul celei reale (forma 1: plata 5000 permisa, verificator "ok" sub o limita reala de 100) cer(/does not hash/.test(arunca(() => openLedger({ identity: idB, policy: lax.policy, policyHash: real.policyHash, now: () => 1000 })) || ''), '15. ATAC: registrul cinstit refuza sa se deschida cu o politica care nu da hash-ul dat'); // adversarul ocoleste biblioteca: scrie registrul sub politica laxa si ii pune in antet si in intrari hash-ul celei reale const Llax = openLedger({ identity: idB, policy: lax.policy, now: () => 1000 }); Llax.record({ kind: 'payment', to: '0xbbbb', amount: '5000' }); // (si sesiunea, derivata din politica: adversarul o rescrie si pe ea, ca sa ajunga la judecata politicii) const sReal = sessionId(idB.agentId, real.policyHash); const falsificat = resemneaza(Llax.export(), 0, (r) => { r.policyHash = real.policyHash; r.session = sReal; r.entries.forEach((e) => { e.body.policyHash = real.policyHash; e.body.session = sReal; }); }); // resemneaza foloseste cheia lui id; aici adversarul e idB, deci re-semnez cu cheia lui for (let k = 0, prev = '0'.repeat(64); k < falsificat.entries.length; k++) { const e = falsificat.entries[k]; e.prev = prev; e.signature = crypto.sign(null, Buffer.from(`${k}|${prev}|${canonical(e.body)}`, 'utf8'), idB.privateKey).toString('base64'); e.hash = crypto.createHash('sha256').update(`${k}|${prev}|${canonical(e.body)}|${e.signature}`).digest('hex'); prev = e.hash; } const vLax = verifyLedger(falsificat, { policy: lax.policy, policyHash: real.policyHash, maxTime: 2000 }); cer(!vLax.ok && /does not hash to the pinned policyHash/.test(vLax.error), `15. ATAC: politica laxa data verificatorului cu hash-ul celei reale -> refuzata (${vLax.error})`); const vReal = verifyLedger(falsificat, { policy: real.policy, policyHash: real.policyHash, maxTime: 2000 }); cer(!vReal.ok && /false decision/.test(vReal.error), '15. cu politica reala, plata de 5000 e prinsa ca decizie falsa (limita 100)'); // 16: antedatare (forma 1: 10 plati de 100 facute in aceeasi secunda, declarate in 10 ferestre trecute, verificator "ok") const acum = 100000; const Lb = openLedger({ identity: idB, policy: real.policy, now: () => acum }); cer(/behind the ledger clock/.test(arunca(() => Lb.record({ kind: 'payment', to: '0xbbbb', amount: '100' }, { at: acum - 36010 })) || ''), '16. ATAC: registrul cinstit refuza la scriere o intrare antedatata cu peste 300 s'); cer(Lb.record({ kind: 'payment', to: '0xbbbb', amount: '100' }, { at: acum - 200 }).allowed, '16. CONTROL: o intrare in toleranta (200 s in urma) se scrie'); // adversarul ocoleste biblioteca: un ceas mincinos care merge in trecut pe masura ce scrie let tb = acum - 36010; const Lant = openLedger({ identity: idB, policy: real.policy, now: () => tb }); let permise = 0; for (let i = 0; i < 10; i++) { tb = acum - 36010 + i * 3601; if (Lant.record({ kind: 'payment', to: '0xbbbb', amount: '100' }).allowed) permise++; } const antedatat = Lant.export(); const vFaraMartor = verifyLedger(antedatat, { policy: real.policy, maxTime: acum + 300 }); cer(permise === 10 && vFaraMartor.ok && /none/.test(vFaraMartor.time.lowerBound), '16. fara martor, antedatarea NU se poate vedea, si rezultatul o spune (lowerBound: none)'); // martorul: capul registrului vazut la 'acum - 3600' (de pilda notarizat), deci intrarile de dupa el nu pot declara mai devreme const cap = { seq: 4, hash: antedatat.entries[4].hash, at: acum - 3600 }; const vAncorat = verifyLedger(antedatat, { policy: real.policy, maxTime: acum + 300, anchors: [cap] }); cer(!vAncorat.ok && vAncorat.seq === 5 && /backdated/.test(vAncorat.error), `16. ATAC: cu un cap ancorat la un martor, prima intrare antedatata de dupa el e prinsa (seq ${vAncorat.seq})`); const vStart = verifyLedger(antedatat, { policy: real.policy, maxTime: acum + 300, notBefore: acum - 7200 }); cer(!vStart.ok && vStart.seq === 0 && /witnessed start/.test(vStart.error), '16. cu inceputul sesiunii vazut de martor (notBefore), intrarile de dinainte sunt prinse'); // controlul pozitiv al ancorelor: un registru cinstit, ancorat la fiecare 3 intrari cu momentul scrierii, trece let tc = acum; const Lc = openLedger({ identity: idB, policy: real.policy, now: () => tc }); const ancore = []; for (let i = 0; i < 6; i++) { tc = acum + i * 1200; const r = Lc.record({ kind: 'payment', to: '0xbbbb', amount: '10' }); if (i % 3 === 2) ancore.push({ seq: r.entry.seq, hash: r.entry.hash, at: tc + 5 }); } cer(verifyLedger(Lc.export(), { policy: real.policy, maxTime: tc + 300, anchors: ancore, notBefore: acum }).ok, '16. CONTROL: un registru cinstit, ancorat, trece cu martorii lui'); // o ancora a altei ramuri (acelasi agent, alta istorie) nu se potriveste const alta = openLedger({ identity: idB, policy: real.policy, now: () => acum }); alta.record({ kind: 'payment', to: '0xbbbb', amount: '11' }); const vRamura = verifyLedger(Lc.export(), { policy: real.policy, maxTime: tc + 300, anchors: [{ seq: 0, hash: alta.export().entries[0].hash, at: acum }] }); cer(!vRamura.ok && /another branch/.test(vRamura.error), '16. CONTROL: o ancora a altei istorii nu se potriveste cu registrul (alta ramura sau alt registru)'); // 17: intrarea poarta sesiunea registrului; o intrare mutata din alt registru nu se potriveste const s2 = resemneaza(reg, 0, (r) => { r.entries[0].body.session = 'f'.repeat(32); }); const vS = verifyLedger(s2, { policy, policyHash, maxTime: t + 300 }); cer(!vS.ok && vS.seq === 0 && /session/.test(vS.error), `17. CONTROL: o intrare care numeste alta sesiune decat registrul -> prinsa (${vS.error})`); const s3 = resemneaza(reg, 0, (r) => { r.session = 'f'.repeat(32); r.entries.forEach((e) => { e.body.session = 'f'.repeat(32); }); }); const vS3 = verifyLedger(s3, { policy, policyHash, maxTime: t + 300 }); cer(!vS3.ok && /derived/.test(vS3.error), `17. ATAC: un registru cu o sesiune aleasa de agent (nu cea derivata), re-semnat -> refuzat (${vS3.error})`); // 18: cheltuiala dubla prin "al doilea registru" = o ramura; doua intrari semnate de agent la aceeasi pozitie sunt dovada const idC = newAgentIdentity(); const pc = definePolicy({ agentId: idC.agentId, spend: { amount: '100', windowSeconds: 3600 }, recipients: ['0xbbbb'] }); const R1 = openLedger({ identity: idC, policy: pc.policy, now: () => 5000 }); const R2 = openLedger({ identity: idC, policy: pc.policy, now: () => 5000 }); const d1 = R1.record({ kind: 'payment', to: '0xbbbb', amount: '100' }), d2 = R2.record({ kind: 'payment', to: '0xbbbb', amount: '99' }); const v1 = verifyLedger(R1.export(), { policy: pc.policy, maxTime: 5300 }), v2 = verifyLedger(R2.export(), { policy: pc.policy, maxTime: 5300 }); cer(d1.allowed && d2.allowed && v1.ok && v2.ok, '18. ATAC: doua registre ale aceluiasi agent, 100 + 99 sub o limita de 100; fiecare, SINGUR, verifica (cine vede o ramura nu o vede pe cealalta)'); const dov = findEquivocation(R1.export(), R2.export()); cer(!!dov && dov.seq === 0 && verifyEquivocation(dov).ok, '18. dar impreuna sunt o dovada de echivocare, verificabila de oricine numai cu cheia publica'); const dovRau = JSON.parse(JSON.stringify(dov)); dovRau.b.body.action.amount = '98'; cer(!verifyEquivocation(dovRau).ok, '18. CONTROL: o "dovada" cu o intrare nesemnata de agent (continut schimbat) e respinsa'); const dovX = { ...JSON.parse(JSON.stringify(dov)), publicKeyPem: idX.publicKey.export({ type: 'spki', format: 'pem' }) }; cer(!verifyEquivocation(dovX).ok, '18. CONTROL: o "dovada" care pune alta cheie publica e respinsa (agentId nu deriva din ea)'); cer(findEquivocation(R1.export(), R1.export()) === null, '18. CONTROL: acelasi registru cu el insusi nu e echivocare'); const R1b = resumeLedger({ identity: idC, policy: pc.policy, ledger: R1.export(), now: () => 5010 }); R1b.record({ kind: 'tool', tool: 'x' }); cer(findEquivocation(R1.export(), R1b.export()) === null, '18. CONTROL: un registru si continuarea lui (prefix comun) nu sunt echivocare'); // 19: repornirea: resumeLedger continua starea (cheltuiala din fereastra, nonce-urile), si refuza un registru care nu verifica cer(!R1b.record({ kind: 'payment', to: '0xbbbb', amount: '1' }).allowed, '19. dupa repornire, cheltuiala din fereastra e pastrata: 100 deja cheltuit, inca 1 -> refuzat'); const R1c = resumeLedger({ identity: idC, policy: pc.policy, ledger: R1b.export(), now: () => 5000 + 3601 }); cer(R1c.record({ kind: 'payment', to: '0xbbbb', amount: '100' }).allowed && verifyLedger(R1c.export(), { policy: pc.policy, maxTime: 9000 }).ok, '19. dupa fereastra, registrul reluat permite iar si ramane verificabil de la capat'); const stricat = JSON.parse(JSON.stringify(R1b.export())); stricat.entries[0].body.action.amount = '1'; cer(/does not verify/.test(arunca(() => resumeLedger({ identity: idC, policy: pc.policy, ledger: stricat, now: () => 5010 })) || ''), '19. CONTROL: un registru atins nu se poate relua'); cer(/another agent/.test(arunca(() => resumeLedger({ identity: idC, policy: pc.policy, ledger: regX, now: () => 5010 })) || ''), '19. CONTROL: registrul altui agent nu se poate relua'); } for (const l of linii) console.log(l); console.log(`agent-ledger: ${ok}/${ok + rau} cum trebuia`); process.exitCode = rau ? 1 : 0;