234 lines
22 KiB
JavaScript
234 lines
22 KiB
JavaScript
// 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
|
|
// (20) martorul probei cheii clasice: un registru de o intrare al unei identitati ML-DSA-65
|
|
const id20 = newAgentIdentity(); const polBun20 = definePolicy({ agentId: id20.agentId, tools: ['retrieval'] });
|
|
const regBun20 = () => { const l = openLedger({ identity: id20, policy: polBun20.policy, policyHash: polBun20.policyHash, now: () => 6000 }); l.record({ kind: 'tool', tool: 'retrieval' }); return l.export(); };
|
|
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');
|
|
}
|
|
|
|
// 20. o identitate de agent e o cheie ML-DSA-65: un registru scris cu o cheie CLASICA (Ed25519), cu agentId derivat din ea si fiecare
|
|
// intrare semnata de ea, e refuzat pe nume. Pana la 2026-10-03 verifica: id-ul se deriva din orice cheie, iar crypto.verify(null, ...)
|
|
// verifica cu orice cheie i se da, deci "identitatea post-cuantica" a agentului putea fi o cheie clasica.
|
|
{
|
|
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519'); const agentId = agentIdFromKey(publicKey);
|
|
const pc = definePolicy({ agentId, tools: ['retrieval'] }); const session = sessionId(agentId, pc.policyHash); const sha = (s) => crypto.createHash('sha256').update(Buffer.from(s, 'utf8')).digest('hex');
|
|
const body = { v: 2, agentId, policyHash: pc.policyHash, session, at: 6000, action: { kind: 'tool', tool: 'retrieval', at: 6000 }, decision: { allowed: true, reason: 'tool allowed' }, provenance: null, seq: 0 };
|
|
const prev = '0'.repeat(64); const signature = crypto.sign(null, Buffer.from(`0|${prev}|${canonical(body)}`, 'utf8'), privateKey).toString('base64');
|
|
const clasic = { version: 2, agentId, publicKeyPem: publicKey.export({ type: 'spki', format: 'pem' }), policyHash: pc.policyHash, session, entries: [{ seq: 0, prev, body, signature, hash: sha(`0|${prev}|${canonical(body)}|${signature}`) }] };
|
|
const v = verifyLedger(clasic, { policy: pc.policy, maxTime: 7000 });
|
|
cer(!v.ok && v.rule === 'cheie' && /not ML-DSA-65/.test(v.error), `20. CONTROL: un registru al unei chei Ed25519 e refuzat pe nume (${v.rule}: ${String(v.error).slice(0, 60)})`);
|
|
cer(/not ML-DSA-65/.test(arunca(() => openLedger({ identity: { agentId, publicKey, privateKey }, policy: pc.policy, policyHash: pc.policyHash, now: () => 6000 })) || ''), '20. CONTROL: nici scriitorul nu deschide un registru pentru o cheie care nu e ML-DSA-65');
|
|
const bun = verifyLedger(regBun20(), { policy: polBun20.policy, maxTime: 7000 });
|
|
cer(bun.ok === true && bun.rule === undefined, '20. martor: acelasi fel de registru, cu o cheie ML-DSA-65, verifica');
|
|
}
|
|
|
|
for (const l of linii) console.log(l);
|
|
console.log(`agent-ledger: ${ok}/${ok + rau} cum trebuia`);
|
|
process.exitCode = rau ? 1 : 0;
|