// Proba lantului ca martor al registrului unui agent (agent-ancora.mjs), OFFLINE: verificatorul AIP-23 e inlocuit de un raspuns in // forma lui (niveluri, finality PASSED cu "as first seen at ()"), ca fiecare garda sa fie judecata fara retea. Drumul // intreg, cu notarizarea reala pe testnetul 28001 si verificatorul AIP-23 publicat, e in proba-agent-ancora-testnet.mjs. // Fiecare afirmatie cu perechea ei negativa. // node proba-agent-ancora.mjs iesire 0 = toate cum trebuia import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { spawnSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import { definePolicy } from './agent-policy.mjs'; import { newAgentIdentity, openLedger, resumeLedger, verifyLedger } from './agent-ledger.mjs'; import { ledgerHeadEnvelope, headMatchesLedger, firstSeenFromVerdict, anchorsFromNotarizedHeads, notarizeHead, HEAD_KIND } from './agent-ancora.mjs'; const AICI = path.dirname(fileURLToPath(import.meta.url)); let ok = 0, rau = 0; const cer = (c, ce, d = '') => { console.log(` ${c ? 'OK ' : 'RAU'} ${ce}${c || !d ? '' : ' -- ' + d}`); c ? ok++ : rau++; }; const clon = (o) => JSON.parse(JSON.stringify(o)); // o exceptie din anchorsFromNotarizedHeads devine un rezultat (stare EXCEPTIE), ca proba sa ajunga la rezumat si randul sa iasa RAU const sigur = async (...a) => { try { return await anchorsFromNotarizedHeads(...a); } catch (e) { return { stare: 'EXCEPTIE: ' + e.message, anchors: [], randuri: [{ detail: String(e.message) }] }; } }; const id = newAgentIdentity(); const { policy, policyHash } = definePolicy({ agentId: id.agentId, spend: { amount: '1000', windowSeconds: 3600 }, recipients: ['0xbbbb'] }); // intrarile 0 si 1 scrise la T0; capul (seq 1) vazut de lant la F = T0 + 1200 const T0 = 1_790_000_000, F = T0 + 1200; let t = T0; const now = () => t; const L = openLedger({ identity: id, policy, policyHash, now }); L.record({ kind: 'payment', to: '0xbbbb', amount: '10' }); t += 5; L.record({ kind: 'payment', to: '0xbbbb', amount: '10' }); const inainte = L.export(); const cap = ledgerHeadEnvelope(inainte, { seq: 1, createdAt: new Date(T0 * 1000 + 6000).toISOString() }); // un raspuns al verificatorului AIP-23 in forma lui, pentru un statementHash dat const verdict = (h, stare = 'PASSED', t2 = F) => ({ verdict: stare === 'PASSED' ? 'VALID' : 'PARTIAL', statementHash: h, levels: [ { level: 'form', state: 'PASSED', detail: 'kind=' + HEAD_KIND + '-attestation' }, { level: 'integrity', state: 'PASSED', detail: 'sha256(statement) == statementHash' }, { level: 'signature', state: 'ABSENT', detail: 'the envelope carries no signature' }, { level: 'finality', state: stare, detail: stare === 'PASSED' ? `post-quantum: the state of block 9, the parent of anchor 10 whose post-quantum certificate verified here, records this statementHash in AereNotary 0x70099E62735500AA2F85B60C518551a57B202d54 as first seen at ${new Date(t2 * 1000).toISOString()} (${t2}); first seen in block 5` : 'notarized at ..., after ...; the next anchor will cover it' }] }); const stub = (raspuns) => async (_vp, f) => raspuns(JSON.parse(fs.readFileSync(f, 'utf8'))); const notarizat = { ...cap, notarization: { chainId: 28001, notary: '0x70099E62735500AA2F85B60C518551a57B202d54', firstSeen: F } }; // ---------------------------------------------------------------- capul cer(cap.kind === HEAD_KIND + '-attestation' && cap.statement.seq === 1 && cap.statement.hash === inainte.entries[1].hash && cap.statement.session === inainte.session, '1. capul numeste agentul, sesiunea, politica, seq si hash-ul intrarii; statementHash = sha256 al declaratiei'); cer(headMatchesLedger(cap, inainte).ok, '1. capul e al registrului'); const alt = clon(cap); alt.statement.hash = '0x' + '11'.repeat(32); cer(!headMatchesLedger(alt, inainte).ok && /modified envelope/.test(headMatchesLedger(alt, inainte).motiv), '1. CONTROL: un cap cu hash-ul schimbat (fara statementHash refacut) -> refuzat'); const id2 = newAgentIdentity(); const { policy: p2, policyHash: h2 } = definePolicy({ agentId: id2.agentId, spend: { amount: '1000', windowSeconds: 3600 }, recipients: ['0xbbbb'] }); const L2 = openLedger({ identity: id2, policy: p2, policyHash: h2, now }); L2.record({ kind: 'payment', to: '0xbbbb', amount: '1' }); L2.record({ kind: 'payment', to: '0xbbbb', amount: '1' }); cer(!headMatchesLedger(cap, L2.export()).ok, '1. CONTROL: capul altui agent nu e al acestui registru'); // un cap cu hash-ul bun dar care numeste alt agent (statementHash refacut): declaratia nu spune adevarul despre registru -> refuzat const altAgent = clon(cap); altAgent.statement.agentId = id2.agentId; altAgent.statementHash = '0x' + (await import('node:crypto')).createHash('sha256').update(JSON.stringify(altAgent.statement)).digest('hex'); cer(!headMatchesLedger(altAgent, inainte).ok && /another agent/.test(headMatchesLedger(altAgent, inainte).motiv), '1. CONTROL: un cap care numeste alt agent (hash-ul intrarii bun, plicul refacut) -> refuzat'); // ---------------------------------------------------------------- momentul citit din verificator const f1 = firstSeenFromVerdict(verdict(cap.statementHash)); cer(f1.stare === 'PASSED' && f1.at === F, '2. momentul firstSeen citit din nivelul de finalitate PASSED (ISO si unix, acelasi moment)'); cer(firstSeenFromVerdict(verdict(cap.statementHash, 'PENDING')).stare === 'NEMASURAT', '2. CONTROL: finalitate in asteptare -> NEMASURAT, nu ancora'); const strica = verdict(cap.statementHash); strica.levels[3].detail = strica.levels[3].detail.replace(`(${F})`, `(${F + 60})`); cer(firstSeenFromVerdict(strica).stare === 'NEMASURAT', '2. CONTROL: ISO si unix care nu spun acelasi moment -> NEMASURAT'); const fara = verdict(cap.statementHash); fara.levels[3].detail = 'post-quantum: recorded'; cer(firstSeenFromVerdict(fara).stare === 'NEMASURAT', '2. CONTROL: un detaliu intr-o forma necunoscuta -> NEMASURAT, nu un moment ghicit'); const cazut = verdict(cap.statementHash); cazut.levels[3].state = 'FAILED'; cer(firstSeenFromVerdict(cazut).stare === 'FAILED', '2. CONTROL: finalitate FAILED -> FAILED'); // ---------------------------------------------------------------- ancorele si verificarea registrului const r = await sigur(inainte, [notarizat], { verifyProof: 'stub', rpc: 'stub', ruleaza: stub((e) => verdict(e.statementHash)) }); cer(r.stare === 'OK' && r.anchors.length === 1 && r.anchors[0].seq === 1 && r.anchors[0].at === F && r.anchors[0].hash === inainte.entries[1].hash, '3. un cap notarizat, cu finalitate post-cuantica -> ancora {seq 1, hash, at = firstSeen}', JSON.stringify(r)); const rH = await sigur(inainte, [notarizat], { verifyProof: 'stub', rpc: 'stub', ruleaza: stub(() => ({ ...verdict('0x' + '22'.repeat(32)) })) }); cer(rH.stare === 'FAILED' && !rH.anchors.length, '3. CONTROL: verificatorul a judecat alt statementHash -> FAILED, nicio ancora'); const rP = await sigur(inainte, [notarizat], { verifyProof: 'stub', rpc: 'stub', ruleaza: stub((e) => verdict(e.statementHash, 'PENDING')) }); cer(rP.stare === 'NEMASURAT' && !rP.anchors.length, '3. CONTROL: finalitate in asteptare -> NEMASURAT, nicio ancora'); const rN = await sigur(inainte, [notarizat], { verifyProof: 'stub', rpc: 'stub', ruleaza: async () => null }); cer(rN.stare === 'NEMASURAT', '3. CONTROL: verificatorul nu raspunde -> NEMASURAT'); const minte = { ...notarizat, notarization: { ...notarizat.notarization, firstSeen: F - 5000 } }; const rM = await sigur(inainte, [minte], { verifyProof: 'stub', rpc: 'stub', ruleaza: stub((e) => verdict(e.statementHash)) }); cer(rM.stare === 'OK' && rM.anchors[0].at === F, '3. CONTROL: momentul ancorei e cel certificat de verificator, nu cel scris in plic (plicul spune F - 5000)', JSON.stringify(rM.anchors)); // dupa notarizare: o intrare cinstita (la F + 10) si una ANTEDATATA (la T0 + 100, sub F - 300), scrise cu cheia agentului t = F + 10; const cinstit = resumeLedger({ identity: id, policy, policyHash, ledger: inainte, now }); cinstit.record({ kind: 'payment', to: '0xbbbb', amount: '10' }); t = T0 + 100; const antedatat = resumeLedger({ identity: id, policy, policyHash, ledger: inainte, now }); antedatat.record({ kind: 'payment', to: '0xbbbb', amount: '10' }); const mt = F + 1000; const vC = verifyLedger(cinstit.export(), { policy, maxTime: mt, anchors: r.anchors }); cer(vC.ok && vC.time.lowerBound === 'witnessed', '4. registrul cinstit cu ancora lantului: ok, marginea de jos "witnessed"', JSON.stringify(vC).slice(0, 200)); const vA0 = verifyLedger(antedatat.export(), { policy, maxTime: mt }); cer(vA0.ok, '4. MARTORUL MASURII: fara ancora, intrarea antedatata trece (asta e golul inchis aici)', JSON.stringify(vA0).slice(0, 200)); const vA = verifyLedger(antedatat.export(), { policy, maxTime: mt, anchors: r.anchors }); cer(!vA.ok && /backdated/.test(vA.error || ''), '4. cu ancora lantului, intrarea antedatata e prinsa ("backdated")', JSON.stringify(vA).slice(0, 200)); // alta ramura a aceluiasi agent (aceeasi sesiune): intrarea 1 diferita; capul notarizat al primei ramuri nu e al ei t = T0; const ramura = openLedger({ identity: id, policy, policyHash, now }); ramura.record({ kind: 'payment', to: '0xbbbb', amount: '10' }); t += 5; ramura.record({ kind: 'payment', to: '0xbbbb', amount: '99' }); const rR = await sigur(ramura.export(), [notarizat], { verifyProof: 'stub', rpc: 'stub', ruleaza: stub((e) => verdict(e.statementHash)) }); cer(rR.stare === 'FAILED' && /another branch/.test(rR.randuri[0].detail), '5. o alta ramura a agentului nu are capul notarizat: FAILED, "another branch"'); cer(!verifyLedger(ramura.export(), { policy, maxTime: mt, anchors: r.anchors }).ok, '5. si verifyLedger refuza ramura cu ancora primei ramuri'); // notarizarea: gardele de dinaintea retelei (un cap atins, alt fel de plic) nu ating reteaua const atins = clon(cap); atins.statement.seq = 0; const n1 = await notarizeHead({ envelope: atins, rpc: 'http://127.0.0.1:9', keyFile: 'nu-exista', ethers: null }); cer(!n1.ok && n1.cod === 1 && /modified envelope/.test(n1.motiv), '6. notarizarea refuza un cap atins, inainte de retea'); const n2 = await notarizeHead({ envelope: { ...cap, kind: 'aere-audit-head-attestation' }, rpc: 'x', keyFile: 'x', ethers: null }); cer(!n2.ok && n2.cod === 2, '6. notarizarea refuza alt fel de plic'); // un nod care serveste 2800 (lantul principal): refuz fara confirmare explicita; lantul se citeste de la nod, nu din argument const eth = (chain, cod) => ({ JsonRpcProvider: class { async getNetwork() { return { chainId: BigInt(chain) }; } async getCode() { return cod; } }, Wallet: class { constructor() { throw new Error('the test never gets this far'); } }, Contract: class {} }); const n3 = await notarizeHead({ envelope: cap, rpc: 'x', keyFile: 'x', ethers: eth(2800, '0x60'), confirmMainnet: false }); cer(!n3.ok && n3.cod === 3 && /mainnet/.test(n3.motiv), '6. un nod al lantului 2800 -> REFUZ fara AERE_CONFIRM_MAINNET=yes (o tranzactie reala)', JSON.stringify(n3)); const n4 = await notarizeHead({ envelope: cap, rpc: 'x', keyFile: 'x', ethers: eth(28001, '0x') }); cer(!n4.ok && n4.cod === 1 && /no contract/.test(n4.motiv), '6. niciun contract la adresa notarului -> REFUZ', JSON.stringify(n4)); // linia de comanda: head scrie exact plicul bibliotecii const T = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-anc-')); try { fs.writeFileSync(path.join(T, 'l.json'), JSON.stringify(inainte)); const c1 = spawnSync(process.execPath, [path.join(AICI, 'agent-ancora.mjs'), 'head', '--ledger', path.join(T, 'l.json'), '--seq', '1', '--out', path.join(T, 'h.json')], { encoding: 'utf8' }); const h = JSON.parse(fs.readFileSync(path.join(T, 'h.json'), 'utf8')); cer(c1.status === 0 && headMatchesLedger(h, inainte).ok && h.statement.seq === 1, '7. `head` din linia de comanda scrie capul registrului', c1.stderr); const c2 = spawnSync(process.execPath, [path.join(AICI, 'agent-ancora.mjs'), 'anchors', '--ledger', path.join(T, 'l.json'), '--head', path.join(T, 'h.json'), '--rpc', 'http://127.0.0.1:9'], { encoding: 'utf8', env: { ...process.env, AERE_VERIFY_PROOF: '' } }); cer(c2.status === 2 && /NEMASURAT/.test(c2.stderr), '7. `anchors` fara verificatorul AIP-23 -> NEMASURAT (cod 2), nu ancore', `${c2.status} ${c2.stderr}`); } finally { fs.rmSync(T, { recursive: true, force: true }); } console.log(`\nagent-ancora: ${ok}/${ok + rau} cum trebuia`); process.exitCode = rau ? 1 : 0;