#!/usr/bin/env node // Proba cap la cap a portofelului-contract 2-din-2 pe testnetul PUBLIC 28001 (2026-09-29, punctele 23, 25): banii agentului stau intr-un // AereAgentWallet2of2 desfasurat aici, si ies numai cu semnatura agentului SI a serviciului de politica, prin facilitatorul x402 al // testnetului (ERC-1271) si prin tokenul EIP-3009 (SignatureChecker). // 1. contractul se desfasoara din artefactul publicat (AereAgentWallet2of2.json), cu doua chei NOI tinute numai in memorie (agentul, // serviciul de politica); codul de pe lant trebuie sa fie EXACT codul compilat cu cei doi semnatari; // 2. primeste 0,05 tUSD de la cheia de dezvoltator a testnetului; // 3. contractul, masurat pe lant (eth_call isValidSignature): DA numai pentru agent || politica; NU pentru fiecare jumatate singura, // dublata, in ordine inversa, sau cu o cheie straina; // 4. trei cumparaturi de 0,01 platite din contract si servite; a patra refuzata de politica; // 5. ATACURI prin facilitatorul testnetului: agentul singur si serviciul de politica singur nu pot plati (402); si TOKENUL insusi, // intrebat pe lant fara facilitator (eth_call transferWithAuthorization): refuza jumatatea agentului, primeste perechea; // 6. pe lant: soldurile, nonce-urile folosite (sha256 al intrarii), iar nonce-ul atacului nefolosit; // 7. dosarul-dovada (cu walletContract) verificat cu verifica-plati.mjs: VALID, inclusiv codul contractului; un dosar care numeste // alt semnatar de politica: INVALID. // Tranzactii trimise de proba: desfasurarea si finantarea (cheia de dezvoltator); decontarile le plateste facilitatorul. Refuza orice // alt lant decat 28001. Cheia de dezvoltator se citeste din AERE_TESTNET_KEY_FILE (d= sau PRIVATE_KEY=0x) si nu se tipareste. // AERE_TESTNET_KEY_FILE= node proba-cosign-testnet.mjs [--rpc URL] [--facilitator URL] import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { definePolicy } from '../agent-policy.mjs'; import { newAgentIdentity, openLedger } from '../agent-ledger.mjs'; import { createWallet, assetId, nonceForEntry, incarcaEthers, EIP3009_TYPES } from './wallet.mjs'; import { payWithAgent } from './client.mjs'; import { createResourceServer } from './resource-server.mjs'; import { verificaPlati } from './verifica-plati.mjs'; import { incarcaArtefact, codulAsteptat, amprentaSursei } from './contract-2of2.mjs'; const AICI = path.dirname(fileURLToPath(import.meta.url)); const a = process.argv.slice(2); const get = (f, d) => { const i = a.indexOf(f); return i >= 0 ? a[i + 1] : d; }; const RPC = get('--rpc', 'https://testnet-rpc.aere.network'); const FAC = get('--facilitator', 'https://testnet-rpc.aere.network/x402'); const NET = 'eip155:28001'; const TOKEN = { address: '0x8215bA247a3574af8EBC36606eB437811E318FBd', name: 'AereTestUSD', version: '2' }; const MAGIC = '0x1626ba7e', INVALID = '0xffffffff'; const ethers = incarcaEthers(); let ok = 0, rau = 0; const cer = (c, ce) => { console.log((c ? ' OK ' : ' RAU ') + ce); c ? ok++ : rau++; return c; }; const taie = (s) => String(s || '').replace(/(0x)?[0-9a-fA-F]{60,}/g, '').slice(0, 160); const kf = process.env.AERE_TESTNET_KEY_FILE; if (!kf || !fs.existsSync(kf)) { console.log('NEMASURAT: AERE_TESTNET_KEY_FILE nu numeste un fisier cu cheia de dezvoltator a testnetului'); process.exit(2); } const rand = fs.readFileSync(kf, 'utf8').split(/\r?\n/).map((l) => l.trim()).find((l) => /^(d|PRIVATE_KEY)=/.test(l)) || ''; const hex = rand.replace(/^(d|PRIVATE_KEY)=/, '').replace(/^0x/, '').trim(); if (!/^[0-9a-fA-F]{1,64}$/.test(hex)) { console.log('NEMASURAT: fisierul cheii nu are un rand d= sau PRIVATE_KEY=0x'); process.exit(2); } const provider = new ethers.JsonRpcProvider(RPC, undefined, { staticNetwork: false }); const lant = Number((await provider.getNetwork()).chainId); if (lant !== 28001) { console.log(`REFUZ: RPC-ul serveste lantul ${lant}; proba ruleaza numai pe testnetul 28001`); process.exit(2); } const dev = new ethers.Wallet('0x' + hex.padStart(64, '0'), provider); const art = incarcaArtefact(); const tUSD = new ethers.Contract(TOKEN.address, ['function transfer(address,uint256) returns (bool)', 'function balanceOf(address) view returns (uint256)', 'function authorizationState(address,bytes32) view returns (bool)', 'function transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes)'], dev); const DOM = { name: TOKEN.name, version: TOKEN.version, chainId: 28001, verifyingContract: TOKEN.address }; const digestul = (au) => ethers.TypedDataEncoder.hash(DOM, EIP3009_TYPES, { ...au, value: BigInt(au.value), validAfter: BigInt(au.validAfter), validBefore: BigInt(au.validBefore) }); const deschise = []; try { // 1. desfasurarea, din artefactul publicat const agentEvm = ethers.Wallet.createRandom(), politica = ethers.Wallet.createRandom(), strain = ethers.Wallet.createRandom(); const payee = ethers.Wallet.createRandom().address; const c = await new ethers.ContractFactory(art.abi, art.bytecode, dev).deploy(agentEvm.address, politica.address); const rcD = await c.deploymentTransaction().wait(1, 120000); const portofel = await c.getAddress(); const cod = String(await provider.getCode(portofel)).toLowerCase(); cer(rcD && rcD.status === 1 && cod === codulAsteptat(art, { agentSigner: agentEvm.address, policySigner: politica.address }), `1. AereAgentWallet2of2 desfasurat la ${portofel} (bloc ${rcD && rcD.blockNumber}): codul de pe lant e exact codul compilat din sursa publicata (sha256 ${String(amprentaSursei(art)).slice(0, 16)}), cu cei doi semnatari`); // 2. finantarea const rc0 = await (await tUSD.transfer(portofel, 50000n)).wait(1, 120000); cer(rc0 && rc0.status === 1 && (await tUSD.balanceOf(portofel)) === 50000n, `2. portofelul-contract primeste 0,05 tUSD (bloc ${rc0 && rc0.blockNumber})`); // 3. contractul, pe lant const w = new ethers.Contract(portofel, art.abi, provider); const h = ethers.hexlify(ethers.randomBytes(32)); const sA = agentEvm.signingKey.sign(h).serialized, sP = politica.signingKey.sign(h).serialized, sS = strain.signingKey.sign(h).serialized; const raspunsuri = { 'agent || politica': await w.isValidSignature(h, ethers.concat([sA, sP])), 'agentul singur (65 de octeti)': await w.isValidSignature(h, sA), 'politica singura (65 de octeti)': await w.isValidSignature(h, sP), 'agentul de doua ori': await w.isValidSignature(h, ethers.concat([sA, sA])), 'politica de doua ori': await w.isValidSignature(h, ethers.concat([sP, sP])), 'ordinea inversa': await w.isValidSignature(h, ethers.concat([sP, sA])), 'agent || cheie straina': await w.isValidSignature(h, ethers.concat([sA, sS])), 'cheie straina || politica': await w.isValidSignature(h, ethers.concat([sS, sP])), }; const [primul, ...restul] = Object.entries(raspunsuri); cer(primul[1] === MAGIC && restul.every(([, r]) => r === INVALID), `3. pe lant, isValidSignature: DA numai pentru agent || politica; NU pentru ${restul.length} alte forme (${restul.filter(([, r]) => r !== INVALID).map(([k]) => k).join(', ') || 'toate refuzate'})`); // 4. cumparaturile, prin facilitatorul testnetului const agent = newAgentIdentity(); const { policy, policyHash } = definePolicy({ agentId: agent.agentId, spend: { amount: '30000', windowSeconds: 3600, asset: assetId(NET, TOKEN.address) }, recipients: [payee], wallet: portofel }); const serviciu = createWallet({ policy, policyHash, evmPrivateKey: politica.privateKey, contractWallet: portofel, network: NET, token: TOKEN }); const L = openLedger({ identity: agent, policy, policyHash }); const cerinta = { scheme: 'exact', network: NET, amount: '10000', asset: TOKEN.address, payTo: payee, maxTimeoutSeconds: 120, extra: { name: TOKEN.name, version: TOKEN.version } }; const rs = createResourceServer({ requirement: cerinta, facilitator: FAC, content: 'the paid content' }); const url = await rs.listen(); deschise.push(rs.server); const plati = []; for (let i = 0; i < 3; i++) plati.push(await payWithAgent({ url, ledger: L, wallet: serviciu, agentEvmKey: agentEvm.privateKey })); cer(plati.every((p) => p.paid && p.status === 200 && p.body === 'the paid content' && /^0x[0-9a-f]{64}$/.test(p.settlement.transaction || '')), `4. trei cumparaturi de 0,01 tUSD platite din portofelul 2-din-2 prin facilitatorul testnetului si servite (${plati.map((p) => p.status + (p.reason ? ' ' + taie(p.reason) : '')).join('; ')})`); const s0 = rs.lastPayloads[0] && rs.lastPayloads[0].payload.signature; cer(!!s0 && ethers.dataLength(s0) === 130, '4. fiecare plata poarta 130 de octeti de semnatura: jumatatea agentului, apoi a serviciului de politica'); const p4 = await payWithAgent({ url, ledger: L, wallet: serviciu, agentEvmKey: agentEvm.privateKey }); cer(!p4.paid && /policy refused.*over the limit/.test(p4.reason || '') && serviciu.status().signed === 3, `4. CONTROL: a patra depaseste 0,03 pe ora: refuzata de politica, serviciul a semnat tot 3 (${taie(p4.reason)})`); // 5. atacurile: o autorizare noua catre vanzator, semnata de o singura parte const acum = Math.floor(Date.now() / 1000); const au = { from: portofel, to: payee, value: '10000', validAfter: String(acum - 5), validBefore: String(acum + 100), nonce: ethers.hexlify(ethers.randomBytes(32)) }; const d5 = digestul(au); const a5 = agentEvm.signingKey.sign(d5).serialized, p5 = politica.signingKey.sign(d5).serialized; const trimite = async (sig) => { const pl = { x402Version: 2, accepted: cerinta, payload: { signature: sig, authorization: au } }; const r = await fetch(url, { headers: { 'PAYMENT-SIGNATURE': Buffer.from(JSON.stringify(pl)).toString('base64') } }); return [r.status, await r.text()]; }; const [cA, tA] = await trimite(ethers.concat([a5, a5])); cer(cA === 402 && /invalid_signature/.test(tA), `5. ATAC: agentul singur (jumatatea lui de doua ori) prin facilitatorul testnetului -> ${cA} (${taie(tA.match(/payment not valid: [a-z_]+/) || tA)})`); const [cP, tP] = await trimite(ethers.concat([p5, p5])); cer(cP === 402 && /invalid_signature/.test(tP), `5. ATAC: serviciul de politica singur (proprietarul) prin facilitatorul testnetului -> ${cP} (${taie(tP.match(/payment not valid: [a-z_]+/) || tP)})`); const apelTok = (sig) => tUSD.transferWithAuthorization.staticCall(au.from, au.to, BigInt(au.value), BigInt(au.validAfter), BigInt(au.validBefore), au.nonce, sig, { from: dev.address }); let refuzAgent = false; try { await apelTok(ethers.concat([a5, a5])); } catch { refuzAgent = true; } let refuzPol = false; try { await apelTok(ethers.concat([p5, p5])); } catch { refuzPol = true; } let primestePerechea = false; try { await apelTok(ethers.concat([a5, p5])); primestePerechea = true; } catch { primestePerechea = false; } cer(refuzAgent && refuzPol && primestePerechea, `5. pe lant, fara facilitator (eth_call transferWithAuthorization): tokenul refuza agentul singur (${refuzAgent}) si politica singura (${refuzPol}), primeste perechea (${primestePerechea})`); rs.server.close(); // 6. pe lant await new Promise((r) => setTimeout(r, 2000)); const soldPayee = await tUSD.balanceOf(payee), soldPortofel = await tUSD.balanceOf(portofel); cer(soldPayee === 30000n && soldPortofel === 20000n, `6. pe lant: vanzatorul are 30000, portofelul-contract 20000 (${soldPayee}, ${soldPortofel})`); const folosite = await Promise.all(plati.map((p) => tUSD.authorizationState(portofel, nonceForEntry(p.entry.hash)))); const atacFolosit = await tUSD.authorizationState(portofel, au.nonce); cer(folosite.every(Boolean) && !atacFolosit, '6. pe lant: nonce-ul sha256(hash-ul intrarii) e folosit pentru fiecare plata; nonce-ul atacurilor nu'); // 7. dosarul-dovada, verificat din afara const dovada = { v: 1, kind: 'aere-agent-x402-payments', network: NET, token: TOKEN.address, wallet: portofel, fromBlock: rcD.blockNumber, walletContract: { contract: 'AereAgentWallet2of2', sourceSha256: amprentaSursei(art), agentSigner: agentEvm.address, policySigner: politica.address, deployTransaction: rcD.hash }, facilitator: FAC, policy, policyHash, ledger: L.export(), payments: plati.map((p) => ({ entrySeq: p.entry.seq, entryHash: p.entry.hash, transaction: p.settlement.transaction })), createdAt: new Date().toISOString() }; const dir = path.join(AICI, 'dovezi-28001'); fs.mkdirSync(dir, { recursive: true }); const f = path.join(dir, `plati-agent-2of2-${dovada.createdAt.slice(0, 19).replace(/[:T]/g, '-')}.json`); fs.writeFileSync(f, JSON.stringify(dovada, null, 1) + '\n'); const v = await verificaPlati(dovada, { rpc: RPC, allTransfers: true }); cer(v.verdict === 'VALID' && v.checks.some((x) => x.pass === true && /is the 2-of-2 contract/.test(x.name)), `7. verifica-plati pe dosarul-dovada: ${v.verdict} (${v.checks.length} verificari, printre ele: codul portofelului e contractul 2-din-2)`); const alt = JSON.parse(JSON.stringify(dovada)); alt.walletContract.policySigner = strain.address; const va = await verificaPlati(alt, { rpc: RPC }); cer(va.verdict === 'INVALID', `7. CONTROL: un dosar care numeste alt semnatar de politica -> ${va.verdict} (codul de pe lant nu e cel cu acel semnatar)`); console.log(` dosarul-dovada: ${path.relative(process.cwd(), f)}`); } catch (e) { cer(false, `proba s-a oprit: ${taie(e.shortMessage || e.message)}`); } finally { for (const s of deschise) { try { s.closeAllConnections && s.closeAllConnections(); s.close(); } catch { /* inchis */ } } } console.log(`\nagent-cosign-testnet: ${ok}/${ok + rau} ${rau ? 'CU ESECURI' : 'cum trebuia'}`); process.exitCode = rau ? 1 : 0;