1763 lines
251 KiB
JavaScript
1763 lines
251 KiB
JavaScript
#!/usr/bin/env node
|
|
// proba-expunere.mjs: proba raportului de expunere cuantica (C12), cu controale negative. Totul local: machetele asculta pe
|
|
// 127.0.0.1, nicio cerere nu pleaca spre afara (si asta e chiar unul din lucrurile masurate).
|
|
//
|
|
// Ce masoara, in ordine (vectorii reali se iau din fixturi/alese.json, dupa ROL, nu scrisi aici):
|
|
// 1. codecurile, cu control pozitiv si negativ: vectorii BIP-173/350 (valizi si invalizi), base58check, tipurile de script, TOATE
|
|
// tranzactiile reale din fixturi refacute octet cu octet si hashuite la id-ul lor, annex-ul BIP-341 (un singur element nu e annex),
|
|
// RLP contra verify-proof.mjs (publicat), vectorul EIP-155 din EIP, tranzactii 0x1/0x3 semnate cu o cheie de proba si recuperate,
|
|
// recuperarea cheii pe toate tranzactiile blocului Ethereum din fixtura (un bit schimbat in r -> alta adresa), autoritatea EIP-7702
|
|
// recuperata din autorizatia reala a blocului, EIP-55
|
|
// 2. fixturile: sursa si data scrise in fisier, fara CR, rolurile din alese.json
|
|
// 3. modul 1 (offline) pe vectorii reali, SUB PAZA DE RETEA: etichetele asteptate, ZERO conexiuni / DNS / UDP / procese in jurnal,
|
|
// plicul VALID la verificatorul de referinta, evidenceHash == amprenta canonica a raportului, intrebarile de declaratie si
|
|
// termenele citate in Markdown, zero cuvinte interzise
|
|
// 4. modul 2 prin cele patru machete (Electrum, Esplora, Bitcoin Core cu si FARA --txids, Ethereum JSON-RPC), cu raspunsuri
|
|
// INREGISTRATE de pe un index public (sursa si data in fixturi/*.json): etichetele si dovezile pe fiecare rol (EIP-7702 pe
|
|
// autoritatea reala, P2TR nefinantat = "no output exists yet" prin Electrum / Esplora si NEMASURAT prin Core, unde nicio tranzactie
|
|
// data nu il atinge; P2PKH-ul necheltuit = F prin Electrum / Esplora si NEMASURAT prin Core ("cannot check that it lists every
|
|
// transaction": prin Core niciodata F); pozitia Core e hash-ul INTREG al blocului; niciun istoric real nu trece de o pagina Esplora, paginarea se
|
|
// masoara in 5d pe un istoric sintetic); jurnalul pazei numara exact conexiunile ACCEPTATE de macheta (un ascultator real), toate
|
|
// spre tinta declarata; plicul VALID
|
|
// 5. controalele negative, fiecare in trei stari (o plantare care nu se poate pune e STRICAT, deci esec):
|
|
// a. cate un furnizor strain pe FIECARE drum numit de paza (fetch, dgram send, dns.promises.resolveTxt, dns.Resolver,
|
|
// dns.promises.Resolver, dns.lookupService, dns.promises.lookupService, dns.promises.reverse pe un literal, process.binding(tcp_wrap),
|
|
// process.dlopen, Worker, Worker-ul original cautat prin reflectie, child_process, clasa ChildProcess, process.execve, o teava cu
|
|
// NUME DE SERVER pe loopback) plantat intr-o COPIE a uneltei e PRINS de paza in modul blocheaza: jurnalul il numeste si il refuza; in
|
|
// modul jurnal un Worker si un copil Node (mediu mostenit) care SE CONECTEAZA local au conexiunea in jurnal (paza se incarca in ei
|
|
// prin --require), un socket UDP conectat e jurnalizat cu adresa lui, iar o conexiune cu lookup propriu are adresa intoarsa de
|
|
// lookup in jurnal; toate tintele modului jurnal sunt 127.0.0.1; unealta FARA paza nu produce jurnal (citirea spune NEMASURAT).
|
|
// Copia plantata NU se ruleaza niciodata fara paza: drumurile ei ar trimite pachete reale (DNS, TCP spre o adresa de documentatie).
|
|
// Trei controale numara ce AJUNGE la un ascultator al probei, fiecare cu plantari inverse intr-o COPIE A PAZEI: TEVI (formele care
|
|
// numesc un server prin '..', prin prefixul NT \??\, prin GLOBALROOT\Device\Mup sau ca sir dat lui socket.connect ajung prin SMB pe
|
|
// loopback la o teava ascultata aici; in blocare ajunge numai forma locala; judecata pe sirul brut si sirul citit ca port le lasa sa
|
|
// ajunga); DNS in configuratia modului 2 (tinta un literal si un nume: reverse / TXT / TLSA / A pe tinta si orice setServers ->
|
|
// zero datagrame la serverele DNS mutate, inainte de paza, pe un ascultator UDP pe 127.0.0.2; regula veche a literalului si lista
|
|
// fixa de nume lasa datagrame); UDP spre tinta declarata in cele trei forme de argumente si prin sendto (ajung 4), spre alt port si o aderare
|
|
// multicast pe loopback refuzate (citirea veche a argumentelor si aderarea neinvelita ies ROSII). Rularile acestea sunt asincrone:
|
|
// cu spawnSync bucla probei sta si teava ar primi numai cat are instante deschise dinainte
|
|
// b. raspuns gol / de eroare (macheta --gol) -> NEMASURAT numit, nu "no on-chain evidence"; PLANTAREA INVERSA (o copie in care
|
|
// raspunsul gol devine istoric gol) trebuie sa faca verificarea aceasta ROSIE, fara sa crape
|
|
// c. tinta muta (macheta --mut) -> NEMASURAT
|
|
// d. cheltuire PLANTATA (adresa sintetica): fara cheltuire F, cu cheltuire E; la fel prin redeem script P2SH in forma P2PKH si prin
|
|
// witness script P2WSH in forma P2PKH (cheia e un push separat); cheltuirea doar in MEMPOOL (Electrum height 0, Esplora
|
|
// status.confirmed false) -> NEMASURAT, iar dupa ce a fost INLOCUITA (RBF, istoricul mai are doar finantarea) -> F cu "broadcast
|
|
// and never confirmed" numit in "not seen" si in intrebarea de declaratie; un P2TR cu iesirea CONFIRMATA si o cheltuire
|
|
// script-path doar in mempool RAMANE E (cheltuirea din mempool nu scade dovada de pe lant), cu plantarea inversa (conditia veche)
|
|
// ROSIE; o IESIRE P2TR numai in mempool -> NEMASURAT, cu plantarea inversa ROSIE; Esplora cu mempool_stats care numara o
|
|
// cheltuire pe care pagina nu o poarta -> NEMASURAT; un witness script fara forma standard: un push de 33 de octeti care e punct
|
|
// valid pe curba dar NU e operandul unei verificari de semnatura -> NEMASURAT ("not verified as keys bound"), cheia legata de
|
|
// OP_CHECKSIG -> E; cheltuiri VAZUTE de tip hash cu cheia in forme rare (hibrida 06/07 in P2PKH si P2WPKH, <sig> <cheie> OP_NOP)
|
|
// -> E, iar o hibrida cu paritatea gresita si o cheltuire fara nicio preimagine -> NEMASURAT, niciodata F, cu plantarea inversa
|
|
// ROSIE; Esplora cu id-ul unei tranzactii inlocuit sau cu o valoare schimbata (campurile re-serializate nu se hashuiesc la id) ->
|
|
// NEMASURAT, cu plantarea inversa ROSIE; pagina a doua Esplora lipsa -> NEMASURAT; o cheltuire VAZUTA numai prin egalitate de
|
|
// hash (P2SH-P2WPKH servita fara witness si fara finantare) -> NEMASURAT, cu witness-ul E, cu plantarea inversa ROSIE (F)
|
|
// e. eticheta interzisa plantata in limbaj.mjs al unei copii -> poarta de limbaj refuza raportul (cod 1, nimic scris); un --subiect
|
|
// care contine un cuvant interzis NU e refuzat (textul cerutului e consemnat, nu e promisiunea uneltei); un corp de eroare SERVIT
|
|
// de tinta cu un cuvant interzis e citat ca [served: ...] si raportul e scris, cu plantarea inversa (citatul judecat) refuzata;
|
|
// la fel un cuvant servit in locul unui IDENTIFICATOR (tx_hash Electrum, txid Esplora, iesire scantxoutset) si intr-un mesaj
|
|
// servit taiat la 200 de caractere, iar un blockhash Core care nu e unul refuza raspunsul; fiecare cu plantarea lui inversa
|
|
// f. modul 3 fara consimtamant -> refuz; modul 3 spre o tinta privata (loopback) -> refuz si ZERO conexiuni acceptate
|
|
// g. raport.json / plic schimbate dupa semnare -> verificaRaport refuza / verificatorul INVALID
|
|
// h. o intrare care cheltuie ALTA iesire P2TR nu e dovada pentru tinta; cheia altei adrese nu e dovada
|
|
// i. Bitcoin Core FARA --txids pe adrese cheltuite -> NEMASURAT numit (niciodata F); PLANTAREA INVERSA (copie in care scanul
|
|
// pretinde ca enumera cheltuirile) face verificarea ROSIE; Core cu --txids INCOMPLET (numai finantarile unei adrese cheltuite,
|
|
// scanul spune 0 necheltuite) -> NEMASURAT ("not among the --txids given"), cu plantarea inversa (spentNotRead fortat 0) ROSIE;
|
|
// Core cu --txids care NU ating o adresa (istoricul unei singure adrese, pe cei 8 vectori) -> zero F, fiecare adresa neatinsa
|
|
// NEMASURAT numit ("none of the N transaction(s) ... touches this address") sau E din scan, cu plantarea inversa (steagul global)
|
|
// ROSIE; un txid pe care nodul nu il serveste -> REFUZ numit inainte de orice masuratoare (id-ul intreg in mesaj, orice alt hex
|
|
// lung mascat); drumul FARA hex (forma decodata vin/vout, re-serializata si hashuita la id) -> aceleasi etichete E pe cele trei
|
|
// roluri (P2WPKH, P2SH multisig, P2WSH multisig), iar o forma decodata cu un camp schimbat -> REFUZ ("hashes to"); tranzactiile
|
|
// date se citesc O DATA (cereri servite = scanuri + txids, nu adrese x txids); o lista care atinge adresa NUMAI printr-o
|
|
// tranzactie din mempool -> NEMASURAT ("touches this address on chain", mempool numit), cu plantarea inversa ROSIE (si F cu plasa
|
|
// scoasa); finantarea unei iesiri inca necheltuite -> NEMASURAT ("cannot check that it lists every transaction"), cu plasa scoasa
|
|
// F; cheltuirea cu cheia intr-un bloc din AFARA lantului activ (blockhash, confirmations 0) -> NEMASURAT, pe lantul activ E, cu
|
|
// plantarea inversa (pe lant = are blockhash) ROSIE (E). Plantarile pe o garda Core scot si plasa de la capat (prin Core niciodata
|
|
// F), ca fiecare garda sa fie aratata singura
|
|
// j. un EOA cu designator EIP-7702 plantat (cod 0xef0100||adresa, nonce 1) -> E, cu tranzactia de autorizare "nevazuta" numita;
|
|
// o fereastra --eth-blocks care incepe dupa varf -> REFUZ (nicio eticheta pe nonce singur); tranzactia 0x4 care poarta autorizatia
|
|
// cu hash-ul inlocuit -> autorizatia nu e dovada (NEMASURAT cu codul gol, E numai prin designator), cu plantarea inversa ROSIE;
|
|
// un cuvant servit in locul hash-ului unei tranzactii, al lui eth_chainId, al r-ului unei semnaturi sau dupa cei 20 de octeti ai
|
|
// delegatului EIP-7702 nu ajunge necitat in raport, fiecare cu plantarea lui inversa (refuzata de poarta de limbaj)
|
|
// 6. textele publicabile: fara cuvintele interzise (limbaj.mjs, singurul care le numeste, e exclus), fara nimic din operarea noastra,
|
|
// fara child_process / worker_threads / process.binding / lookupService / module native in sursele uneltei; fisierele uneltei neatinse
|
|
// de proba; copiile-frate (.expunere-planta-*, .expunere-oglinda-*) sunt ignorate de git; curatenia de la pornire sterge o copie
|
|
// ORFANA (pid mort) si lasa in pace una a unui proces VIU (control pozitiv si negativ, pe doua dosare create aici); o macheta pornita
|
|
// DETASAT de un parinte care iese se opreste singura (paznicul de parinte), cu plantarea inversa (paznic scos) ROSIE; un @noble/curves
|
|
// in forma 2.x gasit primul e ocolit (cod 0 cu 1.x alaturi, cod 2 numit fara), cu plantarea inversa (crapare in import) ROSIE;
|
|
// dosarul de lucru din %TEMP% poarta pid-ul: al unei rulari moarte e sters, al acesteia lasat, cel de forma veche (fara pid) ramane
|
|
// La pornire proba sterge copiile-frate ORFANE (.expunere-planta-<caz>-<pid>, .expunere-oglinda-<caz>-<pid>) si dosarele de lucru ORFANE
|
|
// (expunere-proba-<pid>-* in %TEMP%) lasate de o rulare OMORATA (un SIGKILL nu ruleaza finally) si spune cate; una a carei rulare (pid-ul
|
|
// din nume) inca traieste nu se atinge, fiindca doua probe (sau proba si oglinda ei) pot rula in acelasi timp, si nici dosarul din care
|
|
// ruleaza chiar aceasta proba. Dosarele de lucru de forma veche (fara pid) sunt numite si lasate: nu se poate spune ale cui sunt.
|
|
// Trei stari: VERDE / ROSU / STRICAT. O exceptie oriunde in proba e STRICAT (cod 1), niciodata 2. Iesire: 0 totul verde; 1 ceva ROSU
|
|
// sau STRICAT; 2 NUMAI cand proba nu poate porni (dependenta @noble, fixturi sau verificator lipsa), numit.
|
|
// 7. runda 6 (k.): varful tintei citit inaintea istoricului (Electrum blockchain.headers.subscribe, Esplora /blocks/tip/height +
|
|
// /block-height/:h + /block/:hash/header): cu varful inregistrat (971000) F, cu varful REAL vechi 969686 NEMASURAT "target behind the
|
|
// chain"; un antet cu un octet schimbat, sau care nu se hashuieste la hash-ul varfului, nu e varf; scripturile-surori (acelasi HASH160
|
|
// ca P2PKH / P2WPKH / P2SH-P2WPKH, si cele trei ale cheii unei linii p2pk:): cheia dezvaluita numai la sora P2WPKH da E cu sora
|
|
// numita prin Electrum, Esplora si Core; un ascultator TLS al probei cu un certificat facut aici (SAN cu un cuvant interzis): raport
|
|
// scris, mesajul TLS citat intreg; Esplora care numara o cheltuire din mempool nepurtata langa alta tranzactie din mempool purtata:
|
|
// NEMASURAT. Fiecare cu plantarea inversa ROSIE (varful nejudecat, surori neinterogate, mesaj TLS necitat, regula veche). Sectiunile
|
|
// 3 si 4 cer in plus: a treia intrebare (aceeasi cheie pe alt lant), randul N8 si celelalte lanturi pe fiecare F, varful pe fiecare
|
|
// verdict Electrum / Esplora. Plantarile pe o garda Core si plantarea raspunsului gol scot si plasa "fara varf citit, niciodata F"
|
|
// 8. runda 7 (l.; revizia rundei 6, scratchpad c12rev7): (MEDIU-A) genezele celor cinci retele Bitcoin recalculate din campurile antetului
|
|
// == tabelul uneltei; o tinta cu geneza regtest si varf regtest -> NEMASURAT "serves another chain" pe o adresa mainnet, F pe o adresa
|
|
// bcrt1; un varf signet (minat o data) sub geneza mainnet -> NEMASURAT "above the limit of Bitcoin mainnet", iar sub geneza signet o
|
|
// adresa tb1 masurata si o linie p2pk: NEMASURAT ("mainnet only"); fiecare verdict pe fixturi numeste lantul. (MEDIU-B) un endpoint
|
|
// Ethereum cu capul la blocul 18.000.000 sau care raporteaza eth_syncing -> NEMASURAT "endpoint behind the chain", cu capul fixturii F
|
|
// care numeste capul. (MEDIU-C) --data intr-o zi viitoare -> REFUZ; o zi trecuta (2012-01-01) -> NEMASURAT pe fiecare adresa prin
|
|
// Electrum, Esplora, Ethereum, Core cu si fara --txids; prin Core un bloc cu ora in viitor sau fara ora -> NEMASURAT. (MIC-D) BIP-86
|
|
// pe vectorul din BIP; o linie p2pk: E prin cealalta codificare (P2PKH necomprimat cheltuit, P2PK comprimat) si prin iesirea BIP-86,
|
|
// F cu 12 surori numite si randul N8 numai cu ce nu se poate deriva. (MIC-E) iesire / tranzactie numarata in mempool si nepurtata ->
|
|
// NEMASURAT. Fiecare cu plantarea inversa ROSIE. Rapoartele prin Bitcoin Core se dateaza cu ziua RULARII (scanul citeste nodul la rulare)
|
|
// 9. runda 8 (m.; revizia rundei 7, scratchpad c12rev8): (MEDIU-1) contul Ethereum se citeste NUMAI din eth_getProof la numarul capului,
|
|
// plimbat de la radacina de stare a blocului capului, si din eth_getCode la acelasi numar, hashuit la hash-ul de cod dovedit: o macheta
|
|
// care raspunde ca un echilibrator cu o masina in urma ("header not found" la cap, nonce 0 la 'latest') -> NEMASURAT, o stare de dupa
|
|
// cap la 'latest' (designator) nu eticheteaza contul la cap, un cod nehashuit / o dovada a altei stari / un furnizor fara eth_getProof
|
|
// -> NEMASURAT numit; constructorul de trie al probei pe vectorii Ethereum, plimbatorul uneltei == cel publicat. (MEDIU-2) punctele de
|
|
// control ale mainnet-ului (967680, 969696) == pinii lui btc/antete.mjs; o tinta a unui lant despartit (geneza si limita mainnet, alt
|
|
// bloc la 969696) -> NEMASURAT "serves another chain" prin Electrum, Esplora si Core; un lant care se termina sub 967680 -> NEMASURAT.
|
|
// (MIC) Core e intrebat ce lant serveste (un nod regtest -> NEMASURAT); numaratorile Esplora null / negative / lipsa si iesirile
|
|
// numarate peste cele purtate -> NEMASURAT; o tranzactie care nu atinge scriptul -> NEMASURAT (Esplora si Electrum). Fiecare cu
|
|
// plantarea inversa ROSIE; plantarea limitei de dovada de lucru (l.2) scoate si regula punctului de control, care o acopera
|
|
// Ce NU masoara (NEMASURAT, scris): modul 3 catre un furnizor real (ar trimite adrese unui tert) si adaptoarele pe servere reale; surorile
|
|
// vectorilor reali nu au fost inregistrate (machetele raspund la un script necunoscut ca un index la un script nefolosit: istoric gol), iar
|
|
// varful nu a fost inregistrat odata cu fixturile (e antetul real al blocului 971000, din lantul de antete validat de C6; _sursa.nota).
|
|
// Nici geneza (server.features, /block-height/0: constanta lantului, recalculata aici), nici capul Ethereum si eth_syncing (cea mai devreme
|
|
// ora pe care o poate purta blocul capului, derivata din blocul inregistrat; _sursa.nota). Un Esplora din spatele unui echilibrator poate
|
|
// servi varful si istoricul de pe masini diferite: nicio cerere nu o arata, si proba nu o masoara. Din runda 8: blocurile punctelor de
|
|
// control si lantul nodului Core din fixturi nu au fost inregistrate (sunt pinii lui btc/antete.mjs); forkul probei serveste la 969696
|
|
// antetul REAL al altui bloc mainnet (969686), nu un antet Bitcoin Cash (nu se poate produce offline); starea Ethereum a fixturii e
|
|
// SINTETICA (trie-ul celor patru conturi inregistrate), deci un raspuns eth_getProof real al unui endpoint public nu e masurat; si capul
|
|
// Ethereum insusi nu e verificat fata de consensul lantului (unealta nu face asta).
|
|
// node proba-expunere.mjs
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import crypto from 'node:crypto';
|
|
import { spawn, spawnSync } from 'node:child_process';
|
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
import { verificaLimbaj, ETICHETA } from './limbaj.mjs';
|
|
|
|
// ---------------------------------------------------------------- poate porni?
|
|
// the tool's own node_modules (after `npm install` here) is handed to every copy the test imports or runs, which has none of its own
|
|
{ const nm = path.join(path.dirname(fileURLToPath(import.meta.url)), 'node_modules'); if (!process.env.AERE_EXPUNERE_NODE_MODULES && fs.existsSync(nm)) process.env.AERE_EXPUNERE_NODE_MODULES = nm; }
|
|
let noble; try { noble = await import('./noble.mjs'); } catch (e) { console.log('NEMASURAT: ' + String((e && e.message) || e)); process.exit(2); }
|
|
const { secp256k1 } = noble;
|
|
const B = await import('./bitcoin.mjs');
|
|
const E = await import('./ethereum.mjs');
|
|
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
|
const RAD = path.resolve(AICI, '..', '..', '..');
|
|
// the compliance-report module (deadlines, canonical hashing, report verification): cloud-gateway/control-plane/ in the AERE development
|
|
// tree, control-plane/ in the public copy (AERE_EXPUNERE_TERMENE names another file); without it the test cannot run at all
|
|
const RAPORT_CONF = [process.env.AERE_EXPUNERE_TERMENE, '../../../cloud-gateway/control-plane/raport-conformitate.mjs', '../control-plane/raport-conformitate.mjs'].filter(Boolean).map((p) => path.resolve(AICI, p)).find((p) => fs.existsSync(p));
|
|
if (!RAPORT_CONF) { console.log('NEMASURAT: control-plane/raport-conformitate.mjs (the compliance-report module) is not beside this tool'); process.exit(2); }
|
|
const { sha256Canonic, verificaRaport } = await import(pathToFileURL(RAPORT_CONF).href);
|
|
// the AIP-23 reference verifier (aere-proof-protocol/verify.mjs): compared with, not required (AERE_PROOF_PROTOCOL_VERIFY names it)
|
|
const VERIFY = process.env.AERE_PROOF_PROTOCOL_VERIFY || path.join(RAD, 'tools', 'aere-proof-protocol', 'verify.mjs');
|
|
// runda 8: pinii urmaritorului de antete Bitcoin al scutului (punctele de control ale uneltei se compara cu ei) si plimbatorul publicat de
|
|
// dovezi Merkle-Patricia (cel al uneltei se compara cu el, pe aceleasi dovezi)
|
|
const PINI = process.env.AERE_EXPUNERE_PINI || path.join(RAD, 'tools', 'aere-quantum-shield', 'btc', 'antete.mjs');
|
|
// the anchor verifier (verify-proof.mjs, the published RLP encoder) and its MPT walker (mpt.mjs beside it): AERE_VERIFY_PROOF names it
|
|
const VERIFY_PROOF = process.env.AERE_VERIFY_PROOF || path.join(RAD, 'verificator-falcon', 'verify-proof.mjs');
|
|
const MPT = path.join(path.dirname(VERIFY_PROOF), 'mpt.mjs');
|
|
const PAZA = pathToFileURL(path.join(AICI, 'paza-retea.mjs')).href;
|
|
const MACHETE = path.join(AICI, 'machete.mjs');
|
|
const FIX = path.join(AICI, 'fixturi');
|
|
// data rapoartelor: ziua in care au fost inregistrate fixturile. Varful pe care il servesc machetele (blocul 971000, ora antetului
|
|
// 2026-10-11T22:53:11Z) se judeca fata de SFARSITUL zilei raportului (sau fata de ceasul rularii, daca acela vine primul), cu marja de
|
|
// 2 ore: cu 2026-10-11 referinta e fixa (2026-10-12T00:00:00Z, 67 de minute dupa varf) oricand ar rula proba; cu o data de azi,
|
|
// referinta ar fi ceasul si varful inregistrat ar iesi "target behind the chain"
|
|
const DATA = '2026-10-11';
|
|
const E_ = ETICHETA.EXPUSA, F_ = ETICHETA.FARA_DOVADA, U_ = 'unmeasured';
|
|
for (const f of ['vectori.txt', 'alese.json', 'electrum.json', 'esplora.json', 'bitcoind.json', 'eth-rpc.json'].map((f) => (path.isAbsolute(f) ? f : path.join(FIX, f)))) {
|
|
if (!fs.existsSync(f)) { console.log('NEMASURAT: lipseste ' + f); process.exit(2); }
|
|
}
|
|
// the neighbours of the AERE development tree (the AIP-23 verifier, the shield's Bitcoin header pins, the anchor verifier's RLP encoder
|
|
// and MPT walker) are compared with, not required: in a copy without them those rows are NEMASURAT, named (the exit code stays 0: it
|
|
// is 1 only on a red or broken row, see the end of this file)
|
|
const LIPSA = Object.entries({ VERIFY, PINI, VERIFY_PROOF, MPT }).filter(([, f]) => !fs.existsSync(f)).map(([k]) => k);
|
|
const NUMESTE = { VERIFY: 'AERE_PROOF_PROTOCOL_VERIFY', PINI: 'AERE_EXPUNERE_PINI', VERIFY_PROOF: 'AERE_VERIFY_PROOF', MPT: 'AERE_VERIFY_PROOF, mpt.mjs beside it' };
|
|
const fara = (k) => (LIPSA.includes(k) ? k + ' is not beside this tool (' + NUMESTE[k] + ' names it): NEMASURAT' : null);
|
|
const ALESE = JSON.parse(fs.readFileSync(path.join(FIX, 'alese.json'), 'utf8'));
|
|
const A = ALESE.adrese; const ETHV = ALESE.eth;
|
|
const ROLURI_BTC = ['p2pk', 'p2pkh_cheltuit', 'p2pkh_necheltuit', 'p2wpkh_cheltuit', 'p2sh_multisig_cheltuit', 'p2wsh_multisig_cheltuit', 'p2tr_cheltuit', 'p2tr_nefinantat'];
|
|
if (ROLURI_BTC.some((r) => !A[r]) || !ETHV || !ETHV.expeditor || !ETHV.primitor || !ETHV.contract || !ETHV.autoritate7702) { console.log('NEMASURAT: alese.json nu numeste toate rolurile'); process.exit(2); }
|
|
// dosarul de lucru al probei (in %TEMP%) poarta pid-ul rularii, ca si copiile-frate: o rulare OMORATA il lasa, si urmatoarea il sterge
|
|
const numeT = (pid) => fs.mkdtempSync(path.join(os.tmpdir(), `expunere-proba-${pid}-`));
|
|
const T = numeT(process.pid);
|
|
// copiile-frate ale unei rulari OMORATE (un SIGKILL nu ruleaza finally) ar ramane in arbore: se sterg la pornire, si se spune cate. Numele
|
|
// poarta pid-ul rularii care le-a facut; una al carei proces inca traieste (alta proba, oglinda) nu se atinge, nici dosarul acestei probe
|
|
const pidViu = (pid) => { try { process.kill(pid, 0); return true; } catch (e) { return e.code !== 'ESRCH'; } };
|
|
/** par: dosarul de cercetat; tipar: numele care se cauta; pidDin: pid-ul din nume (null = fara pid, lasat in pace si numit) */
|
|
function curataOrfane(par, tipar = /^\.expunere-(planta|oglinda)-/, pidDin = (f) => { const m = /-(\d+)$/.exec(f); return m ? Number(m[1]) : null; }) {
|
|
const eu = path.resolve(AICI); const sterse = [], lasate = [];
|
|
for (const f of fs.readdirSync(par).filter((x) => tipar.test(x))) {
|
|
const d = path.resolve(par, f); const pid = pidDin(f);
|
|
if (d === eu || d === path.resolve(T)) continue;
|
|
if (pid === null) { lasate.push(f + ' (fara pid in nume)'); continue; }
|
|
if (pidViu(pid)) { lasate.push(f + ' (procesul ' + pid + ' traieste)'); continue; }
|
|
try { fs.rmSync(d, { recursive: true, force: true }); sterse.push(f); } catch (e) { lasate.push(f + ' (nu s-a putut sterge: ' + String((e && e.code) || e).slice(0, 40) + ')'); }
|
|
}
|
|
return { sterse, lasate };
|
|
}
|
|
// dosarele de lucru din %TEMP%: expunere-proba-<pid>-<sufixul mkdtemp>; cele de forma veche (fara pid, inainte de 2026-10-12) sunt numite, nu sterse
|
|
const TIPAR_T = /^expunere-proba-/;
|
|
const pidDinT = (f) => { const m = /^expunere-proba-(\d+)-[A-Za-z0-9]+$/.exec(f); return m ? Number(m[1]) : null; };
|
|
for (const [par, ce, tipar, pidDin] of [[path.join(AICI, '..'), 'copie(i)-frate', undefined, undefined], [os.tmpdir(), 'dosar(e) de lucru in ' + os.tmpdir(), TIPAR_T, pidDinT]]) {
|
|
const c = curataOrfane(par, tipar, pidDin);
|
|
if (c.sterse.length) console.log(`curatenie: ${c.sterse.length} ${ce} orfane din rulari omorate, sterse: ${c.sterse.join(', ')}`);
|
|
if (c.lasate.length) console.log(`curatenie: ${ce}, lasate in pace: ${c.lasate.join(', ')}`);
|
|
}
|
|
|
|
// ---------------------------------------------------------------- contorul, cu trei stari
|
|
let verzi = 0, rosii = 0, stricate = 0, nemasurate = 0;
|
|
const cer = (eticheta, stare, detaliu = '') => {
|
|
if (stare === 'VERDE') verzi++; else if (stare === 'ROSU') rosii++; else if (stare === 'STRICAT') stricate++; else nemasurate++;
|
|
console.log(` [${stare.padEnd(9)}] ${eticheta}${detaliu ? ' ' + String(detaliu).slice(0, 220) : ''}`);
|
|
};
|
|
const da = (c) => (c ? 'VERDE' : 'ROSU');
|
|
const msg = (e) => String((e && e.message) || e).slice(0, 160);
|
|
/** o verificare: f intoarce true/false sau [bool, detaliu]; o exceptie e STRICAT (numarata ca esec) */
|
|
const incearca = async (eticheta, f) => { try { const r = await f(); if (r && typeof r === 'object' && !Array.isArray(r) && r.nemasurat) { cer(eticheta, 'NEMASURAT', r.nemasurat); return; } const [ok, det] = Array.isArray(r) ? r : [r, '']; cer(eticheta, ok === true ? 'VERDE' : ok === false ? 'ROSU' : 'STRICAT', ok === true || ok === false ? det : String(ok)); } catch (e) { cer(eticheta, 'STRICAT', 'exceptie: ' + msg(e)); } };
|
|
/** o sectiune intreaga: o exceptie in ea e un STRICAT numit, si proba merge mai departe */
|
|
const pas = async (nume, f) => { console.log(nume); try { await f(); } catch (e) { cer(nume + ': sectiunea a aruncat', 'STRICAT', msg(e)); } };
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
const vectori = fs.readFileSync(path.join(FIX, 'vectori.txt'), 'utf8').split(/\r?\n/).map((l) => l.replace(/#.*$/, '').trim()).filter(Boolean);
|
|
const scrieLista = (nume, lista) => { const f = path.join(T, nume + '.txt'); fs.writeFileSync(f, lista.join('\n') + '\n'); return f; };
|
|
const LISTA_TOT = scrieLista('tot', vectori), LISTA_BTC = scrieLista('btc', vectori.filter((v) => !v.startsWith('0x'))), LISTA_ETH = scrieLista('eth', vectori.filter((v) => v.startsWith('0x')));
|
|
|
|
// ---------------------------------------------------------------- unelte: machete, rulare sub paza, citiri
|
|
const machete = []; let nMachete = 0;
|
|
async function porneste(fel, fixtura, extra = []) {
|
|
const n = ++nMachete; const gata = path.join(T, `gata-${n}.json`), contor = path.join(T, `contor-${n}.json`);
|
|
const ch = spawn(process.execPath, [MACHETE, '--fel', fel, '--fixtura', fixtura, '--gata', gata, '--contor', contor, ...extra], { stdio: 'ignore', windowsHide: true });
|
|
machete.push(ch);
|
|
for (let i = 0; i < 100; i++) { if (fs.existsSync(gata) && fs.statSync(gata).size > 0) break; await sleep(100); }
|
|
if (!fs.existsSync(gata)) throw new Error('macheta ' + fel + ' nu a pornit');
|
|
const { port } = JSON.parse(fs.readFileSync(gata, 'utf8'));
|
|
return { port, tinta: `127.0.0.1:${port}`, url: `http://127.0.0.1:${port}`, contor: () => JSON.parse(fs.readFileSync(contor, 'utf8')), opreste: () => { try { ch.kill(); } catch {} } };
|
|
}
|
|
let nRulari = 0;
|
|
// pazaFisier: o COPIE a pazei (plantarile inverse pe paza); preincarcate: module CommonJS incarcate cu --require, care ruleaza INAINTEA
|
|
// pazei (--require vine inaintea lui --import, masurat pe Node 24), de exemplu serverele DNS mutate pe un ascultator local
|
|
// data: ziua raportului. Implicit DATA (ziua fixturilor, referinta fixa); prin Bitcoin Core ziua RULARII, fiindca scanul citeste multimea
|
|
// necheltuita a nodului la momentul rularii, si un raport datat cu o zi incheiata de peste 2 ore nu se mai sprijina pe el (runda 7, C)
|
|
function pregatesteRulare({ dir = AICI, args, tinte = '', mod = 'blocheaza', paza = true, pazaFisier = null, preincarcate = [], env = {}, data = null }) {
|
|
const n = ++nRulari; const jurnal = path.join(T, `jurnal-${n}.jsonl`); const iesire = path.join(T, `iesire-${n}`);
|
|
const zi = data ?? (args.includes('--bitcoind') ? new Date().toISOString().slice(0, 10) : DATA);
|
|
return { jurnal, iesire, argv: [...preincarcate.flatMap((f) => ['--require', f]), ...(paza ? ['--import', pazaFisier ? pathToFileURL(pazaFisier).href : PAZA] : []), path.join(dir, 'raport.mjs'), ...args, '--iesire', iesire, '--data', zi], env: { ...process.env, NODE_OPTIONS: '', AERE_PAZA_JURNAL: jurnal, AERE_PAZA_TINTE: tinte, AERE_PAZA_MOD: mod, ...env } };
|
|
}
|
|
function ruleaza(o) {
|
|
const { jurnal, iesire, argv, env } = pregatesteRulare(o);
|
|
const r = spawnSync(process.execPath, argv, { encoding: 'utf8', timeout: 120000, windowsHide: true, env });
|
|
return rezultatRulare(r, jurnal, iesire);
|
|
}
|
|
/** aceeasi rulare, fara sa blocheze bucla probei: un ascultator din proba (o teava locala) poate accepta conexiuni cat ruleaza unealta.
|
|
* Cu spawnSync bucla sta, iar o teava Windows primeste numai cat are instante deschise dinainte (4 la libuv), deci ar numara mai putin */
|
|
function ruleazaAsincron(o) {
|
|
const { jurnal, iesire, argv, env } = pregatesteRulare(o);
|
|
return new Promise((res) => {
|
|
const ch = spawn(process.execPath, argv, { windowsHide: true, env }); let out = '', err = '';
|
|
ch.stdout.on('data', (d) => { out += d; }); ch.stderr.on('data', (d) => { err += d; });
|
|
const t = setTimeout(() => { try { ch.kill(); } catch {} }, 120000);
|
|
ch.on('close', (status) => { clearTimeout(t); res(rezultatRulare({ status, stdout: out, stderr: err }, jurnal, iesire)); });
|
|
});
|
|
}
|
|
function rezultatRulare(r, jurnal, iesire) {
|
|
const j = citesteJurnal(jurnal);
|
|
const are = fs.existsSync(path.join(iesire, 'raport.json'));
|
|
let raport = null; try { raport = are ? JSON.parse(fs.readFileSync(path.join(iesire, 'raport.json'), 'utf8')) : null; } catch { raport = null; }
|
|
return { cod: r.status, out: r.stdout || '', err: r.stderr || '', jurnal: j, iesire, scris: are, raport, md: are ? fs.readFileSync(path.join(iesire, 'raport.md'), 'utf8') : '', plic: are ? path.join(iesire, 'plic-aip23.json') : null };
|
|
}
|
|
function citesteJurnal(f) {
|
|
if (!fs.existsSync(f)) return null;
|
|
const linii = fs.readFileSync(f, 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
|
|
if (!linii.some((x) => x.ev === 'incarcat')) return null;
|
|
const de = (ev) => linii.filter((x) => x.ev === ev);
|
|
return { conexiuni: de('connect'), dns: de('dns'), udp: de('udp'), worker: de('worker'), child: de('child'), nepermise: linii.filter((x) => x.allowed === false), toate: linii };
|
|
}
|
|
// a row judged by the reference verifier: without the verifier the row is NEMASURAT (named), whatever the expression says
|
|
const cerVerif = (et, stare, det) => (fara('VERIFY') ? cer(et, 'NEMASURAT', fara('VERIFY')) : cer(et, stare, det));
|
|
function verdictVerificator(plic) {
|
|
if (fara('VERIFY')) return 'FARA-VERIFICATOR';
|
|
const r = spawnSync(process.execPath, [VERIFY, plic, '--json'], { encoding: 'utf8', timeout: 60000, windowsHide: true, env: { ...process.env, NODE_OPTIONS: '' } });
|
|
try { return JSON.parse(r.stdout).verdict; } catch { return 'FARA-IESIRE(' + (r.stderr || '').slice(0, 80) + ')'; }
|
|
}
|
|
const gaseste = (rap, adresa) => (rap && Array.isArray(rap.addresses) ? rap.addresses.find((x) => x.input === adresa) : null) || null;
|
|
const eticheta = (rap, adresa) => { const a = gaseste(rap, adresa); return a ? (a.unmeasured ? U_ : a.label) : '(lipsa)'; };
|
|
const dovada = (rap, adresa, fragment) => { const a = gaseste(rap, adresa); return !!a && Array.isArray(a.evidence) && a.evidence.some((e) => String(e).includes(fragment)); };
|
|
const nevazut = (rap, adresa, re) => { const a = gaseste(rap, adresa); return !!a && Array.isArray(a.notSeen) && a.notSeen.some((n) => re.test(String(n))); };
|
|
const nemasuratCu = (rap, adresa, fragment) => { const a = gaseste(rap, adresa); return !!a && !!a.unmeasured && String(a.unmeasured).includes(fragment); };
|
|
const detaliu = (rap, adresa) => { const a = gaseste(rap, adresa); return a ? String(a.unmeasured ?? a.label ?? '') : '(lipsa)'; };
|
|
/** etichetele asteptate, pe roluri -> { ok, rele } */
|
|
function compara(rap, asteptari) {
|
|
const rele = Object.entries(asteptari).filter(([a, e]) => eticheta(rap, a) !== e).map(([a, e]) => `${a.slice(0, 14)}: ${eticheta(rap, a)} (asteptat ${e})`);
|
|
return { ok: rele.length === 0, rele: rele.join('; ') };
|
|
}
|
|
const asteapta = (rap, asteptari, nume) => { const c = compara(rap, asteptari); cer(`${nume}: etichetele celor ${Object.keys(asteptari).length} vectori sunt cele asteptate`, da(c.ok), c.rele); };
|
|
// copii ale uneltei pentru plantari: FRATE al dosarului (importurile relative raman valabile), sters la sfarsit
|
|
const copii = [];
|
|
function copie(nume) { const d = path.join(AICI, '..', `.expunere-planta-${nume}-${process.pid}`); fs.cpSync(AICI, d, { recursive: true, filter: (s) => !path.basename(s).startsWith('.expunere-planta-') && path.basename(s) !== 'node_modules' }); copii.push(d); return d; }
|
|
/** inlocuieste EXACT o data; intoarce numarul de potriviri gasite (1 = plantat) */
|
|
function planteaza(fisier, vechi, nou) { const s = fs.readFileSync(fisier, 'utf8'); const n = s.split(vechi).length - 1; if (n !== 1) return n; fs.writeFileSync(fisier, s.replace(vechi, nou)); return 1; }
|
|
const amprenta = (f) => crypto.createHash('sha256').update(fs.readFileSync(f)).digest('hex');
|
|
const FISIERE_UNEALTA = ['raport.mjs', 'limbaj.mjs', 'clasifica.mjs', 'adaptoare.mjs', 'bitcoin.mjs', 'ethereum.mjs', 'noble.mjs', 'paza-retea.mjs', 'machete.mjs'];
|
|
const amprenteInainte = Object.fromEntries(FISIERE_UNEALTA.map((f) => [f, amprenta(path.join(AICI, f))]));
|
|
// sintetice: chei AERE-SINTETIC, adrese si tranzactii construite aici (nicio cheie reala)
|
|
const cheie = (et) => { const priv = crypto.createHash('sha256').update('AERE-SINTETIC expunere ' + et).digest(); return { priv, pub: Buffer.from(secp256k1.getPublicKey(priv, true)) }; };
|
|
const b58 = (b) => { const AL = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; let n = BigInt('0x' + b.toString('hex')); let s = ''; while (n > 0n) { s = AL[Number(n % 58n)] + s; n /= 58n; } for (const x of b) { if (x !== 0) break; s = '1' + s; } return s; };
|
|
const b58check = (ver, h) => { const body = Buffer.concat([Buffer.from([ver]), h]); return b58(Buffer.concat([body, B.sha256d(body).subarray(0, 4)])); };
|
|
const push = (d) => Buffer.concat([Buffer.from([d.length]), d]);
|
|
const SIG = Buffer.concat([Buffer.from([0x30, 0x44, 0x02, 0x20]), Buffer.alloc(32, 7), Buffer.from([0x02, 0x20]), Buffer.alloc(32, 9), Buffer.from([0x01])]);
|
|
const p2pkhSpk = (h) => Buffer.concat([Buffer.from([0x76, 0xa9, 0x14]), h, Buffer.from([0x88, 0xac])]);
|
|
const finantare = (spk, et) => { const tx = { version: 1, vin: [{ txid: crypto.createHash('sha256').update('finantare ' + et).digest('hex'), vout: 0, scriptSig: Buffer.from([0x51]), sequence: 0xffffffff, witness: [] }], vout: [{ value: 100000n, scriptPubKey: spk }], locktime: 0 }; const raw = B.serializeTx(tx); return { raw, id: B.parseTx(raw).txid }; };
|
|
const cheltuire = (idF, scriptSig, witness) => { const tx = { version: 1, vin: [{ txid: idF, vout: 0, scriptSig, sequence: 0xffffffff, witness }], vout: [{ value: 90000n, scriptPubKey: Buffer.from('0014' + 'ab'.repeat(20), 'hex') }], locktime: 0 }; const raw = B.serializeTx(tx); return { raw, id: B.parseTx(raw).txid }; };
|
|
const suprascriereElectrum = (nume, spk, lista) => { const sh = B.scripthash(spk); const r = { ['blockchain.scripthash.get_history:' + sh]: lista.map((x) => ({ tx_hash: x.id, height: x.height })) }; for (const x of lista) r['blockchain.transaction.get:' + x.id] = x.raw.toString('hex'); const f = path.join(T, nume + '.json'); fs.writeFileSync(f, JSON.stringify({ raspunsuri: r })); return f; };
|
|
async function electrumCu(fisierSuprascriere, lista) { const m = await porneste('electrum', path.join(FIX, 'electrum.json'), ['--suprascrie', fisierSuprascriere]); const r = ruleaza({ args: ['--mod', '2', '--adrese', lista, '--electrum', m.tinta], tinte: m.tinta }); m.opreste(); return r; }
|
|
// o tranzactie in forma Esplora (/address/:a/txs), din octetii ei; height 0 = in mempool (status.confirmed false, fara block_height)
|
|
const esploraTx = (x) => { const tx = B.parseTx(x.raw); const conf = x.height > 0; return { txid: x.txid || x.id, version: tx.version, locktime: tx.locktime, size: x.raw.length, weight: x.raw.length * 4, fee: 0, status: conf ? { confirmed: true, block_height: x.height, block_hash: '00'.repeat(32), block_time: 0 } : { confirmed: false }, vin: tx.vin.map((v) => ({ txid: v.txid, vout: v.vout, scriptsig: B.hex(v.scriptSig), witness: v.witness.map(B.hex), prevout: null, is_coinbase: false, sequence: v.sequence })), vout: tx.vout.map((o) => ({ scriptpubkey: B.hex(o.scriptPubKey), value: Number(o.value) })) }; };
|
|
/** an Esplora fixture override for one address: stats + the first page (mempool entries first, then confirmed, newest first), as the index serves them;
|
|
* funded_txo_count counts the confirmed outputs that pay the address (round 8: the adapter compares it with the outputs the pages carry) */
|
|
const suprascriereEsplora = (nume, adresa, lista) => { const conf = lista.filter((x) => x.height > 0).sort((a, b) => b.height - a.height), neconf = lista.filter((x) => !(x.height > 0)); const tA = B.decodeazaIntrareBitcoin(adresa); const fin = conf.reduce((n, x) => n + B.parseTx(x.raw).vout.filter((v) => B.platesteTinta(tA, v.scriptPubKey)).length, 0); const stats = { address: adresa, chain_stats: { funded_txo_count: fin, funded_txo_sum: 0, spent_txo_count: 0, spent_txo_sum: 0, tx_count: conf.length }, mempool_stats: { funded_txo_count: 0, funded_txo_sum: 0, spent_txo_count: neconf.length, spent_txo_sum: 0, tx_count: neconf.length } }; const f = path.join(T, nume + '.json'); fs.writeFileSync(f, JSON.stringify({ raspunsuri: { ['/address/' + adresa]: stats, ['/address/' + adresa + '/txs']: [...neconf, ...conf].map(esploraTx) } })); return f; };
|
|
async function esploraCu(fisierSuprascriere, lista) { const m = await porneste('esplora', path.join(FIX, 'esplora.json'), ['--suprascrie', fisierSuprascriere]); const r = ruleaza({ args: ['--mod', '2', '--adrese', lista, '--esplora', m.url], tinte: m.tinta }); m.opreste(); return r; }
|
|
// predicatele reutilizate de plantarile inverse (aceeasi judecata pe iesirea reala si pe cea plantata)
|
|
const predicatGol = (r) => r.cod === 2 && !!r.raport && r.raport.summary.unmeasured === r.raport.addresses.length && r.raport.summary.noOnChainEvidence === 0 && r.raport.summary.exposed === 0 && r.raport.addresses.every((a) => /^not measured:/.test(String(a.unmeasured)));
|
|
const bitcoindFix = JSON.parse(fs.readFileSync(path.join(FIX, 'bitcoind.json'), 'utf8'));
|
|
const necheltuite = (rol) => { const k = rol === 'p2pk' ? 'scantxoutset:raw(' + B.hex(B.decodeazaIntrareBitcoin(A.p2pk).scriptPubKey) + ')' : 'scantxoutset:' + A[rol]; const v = bitcoindFix.raspunsuri[k]; return v && Array.isArray(v.unspents) ? v.unspents.length : 0; };
|
|
const ASTEPT_CORE_FARA_TXIDS = Object.fromEntries(ROLURI_BTC.map((rol) => [A[rol], (rol === 'p2pk' || rol.startsWith('p2tr')) ? (necheltuite(rol) > 0 ? E_ : U_) : U_]));
|
|
// motivul cerut e chiar cel al lipsei listei ("spends not searched" / "sees only unspent outputs"), nu oricare motiv Core: plasa de la
|
|
// capat (prin Core niciodata F) ar tine eticheta si fara garda listei, dar cu un motiv care vorbeste despre o lista nedata
|
|
const predicatCoreFaraTxids = (r) => !!r.raport && compara(r.raport, ASTEPT_CORE_FARA_TXIDS).ok && r.raport.summary.noOnChainEvidence === 0 && ROLURI_BTC.filter((rol) => eticheta(r.raport, A[rol]) === U_).every((rol) => nemasuratCu(r.raport, A[rol], 'no address index') && nemasuratCu(r.raport, A[rol], '--txids') && (nemasuratCu(r.raport, A[rol], 'spends not searched') || nemasuratCu(r.raport, A[rol], 'sees only unspent outputs')));
|
|
const ASTEPT_MOD1 = { [A.p2pk]: E_, [A.p2pkh_cheltuit]: F_, [A.p2pkh_necheltuit]: F_, [A.p2wpkh_cheltuit]: F_, [A.p2sh_multisig_cheltuit]: F_, [A.p2wsh_multisig_cheltuit]: F_, [A.p2tr_cheltuit]: E_, [A.p2tr_nefinantat]: E_, [ETHV.expeditor]: F_, [ETHV.primitor]: F_, [ETHV.contract]: F_, [ETHV.autoritate7702]: F_ };
|
|
// P2TR nefinantat: fara nicio iesire pe lant eticheta e F ("no output exists yet"); daca intre timp cineva l-a finantat, fixtura o arata si asteptarea devine E
|
|
const P2TR_NEF_ARE_IESIRI = !!(ALESE.rezumat && ALESE.rezumat.p2tr_nefinantat && ALESE.rezumat.p2tr_nefinantat.txCount > 0);
|
|
const ASTEPT_BTC = { [A.p2pk]: E_, [A.p2pkh_cheltuit]: E_, [A.p2pkh_necheltuit]: F_, [A.p2wpkh_cheltuit]: E_, [A.p2sh_multisig_cheltuit]: E_, [A.p2wsh_multisig_cheltuit]: E_, [A.p2tr_cheltuit]: E_, [A.p2tr_nefinantat]: P2TR_NEF_ARE_IESIRI ? E_ : F_ };
|
|
// Bitcoin Core cu TOATE txids din fixtura: P2TR-ul nefinantat nu e atins de nicio tranzactie data, iar scanul nu ii gaseste iesiri: Core
|
|
// nu are index de adrese, deci nu se stie daca a avut iesiri cheltuite -> NEMASURAT ("none of the N transaction(s) ... touches"), nu F.
|
|
// Si P2PKH-ul niciodata cheltuit: istoricul lui citit prin Core e lista data, pe care niciun raspuns al nodului nu o arata completa ->
|
|
// NEMASURAT ("cannot check that it lists every transaction"), nu F: prin Core o adresa e E sau NEMASURAT, niciodata F
|
|
const NU_E_COMPLETA = 'this tool cannot check that it lists every transaction of this address';
|
|
const ASTEPT_CORE_TX = { ...ASTEPT_BTC, [A.p2pkh_necheltuit]: U_, [A.p2tr_nefinantat]: P2TR_NEF_ARE_IESIRI ? E_ : U_ };
|
|
const ASTEPT_ETH = { [ETHV.expeditor]: E_, [ETHV.primitor]: F_, [ETHV.contract]: F_, [ETHV.autoritate7702]: E_ };
|
|
// runda 8: STAREA pe care o serveste o macheta Ethereum la un cap: un trie al conturilor date (ethereum.mjs stareConturi), radacina lui in
|
|
// blocul capului, dovada eth_getProof a fiecarui cont la numarul capului si, pentru un cont cu cod, codul la acelasi numar. Unealta citeste
|
|
// un cont NUMAI asa (adaptoare.mjs ethRpc), deci o plantare de cont se scrie ca o stare, nu ca un raspuns la 'latest'
|
|
const ethFixR = JSON.parse(fs.readFileSync(path.join(FIX, 'eth-rpc.json'), 'utf8')).raspunsuri;
|
|
const CAP_ETH = ethFixR['eth_blockNumber:[]'];
|
|
const stareEth = (conturi, cap = CAP_ETH, timp = ethFixR[`eth_getBlockByNumber:${JSON.stringify([CAP_ETH, false])}`].timestamp) => { const st = E.stareConturi(conturi); const r = { [`eth_getBlockByNumber:${JSON.stringify([cap, false])}`]: { number: cap, timestamp: timp, stateRoot: st.stateRoot } }; for (const c of conturi) { r[`eth_getProof:${JSON.stringify([c.address, [], cap])}`] = st.dovada(c.address); if (c.code && c.code !== '0x') r[`eth_getCode:${JSON.stringify([c.address, cap])}`] = c.code; } return r; };
|
|
let m1 = null; let txs = {};
|
|
// istoricul unei adrese din tranzactiile reale ale fixturii (umplute in sectiunea 1): finantarile ei si cheltuirile iesirilor ei
|
|
const istoricCore = (adresa) => { const t = B.decodeazaIntrareBitcoin(adresa); const toate = Object.values(txs); const iesiri = new Set(); const fin = toate.filter((tx) => tx.vout.some((o) => B.platesteTinta(t, o.scriptPubKey))); for (const tx of fin) tx.vout.forEach((o, i) => { if (B.platesteTinta(t, o.scriptPubKey)) iesiri.add(tx.txid + ':' + i); }); const chelt = toate.filter((tx) => tx.vin.some((v) => iesiri.has(v.txid + ':' + v.vout))); return { fin: fin.map((x) => x.txid), chelt: chelt.map((x) => x.txid) }; };
|
|
|
|
try {
|
|
// ================================================================ 1. codecuri
|
|
await pas('1. codecurile', async () => {
|
|
cer('BIP-173: vectorii valizi (majuscule si minuscule) decodeaza', da(!!B.segwitDecode('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4') && !!B.segwitDecode('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4')));
|
|
cer('BIP-350: adresa v1 cu bech32m decodeaza, aceeasi cu constanta bech32 e REFUZATA', da(!!B.segwitDecode('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y') && B.segwitDecode('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx') === null));
|
|
cer('bech32: suma de control gresita si cazul amestecat sunt REFUZATE', da(B.segwitDecode('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5') === null && B.segwitDecode('bc1qW508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4') === null));
|
|
cer('base58check: adresa buna decodeaza, un caracter schimbat e REFUZAT', da(!!B.base58checkDecode('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa') && B.base58checkDecode('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNb') === null));
|
|
const tipAsteptat = { p2pk: 'p2pk', p2pkh_cheltuit: 'p2pkh', p2pkh_necheltuit: 'p2pkh', p2wpkh_cheltuit: 'p2wpkh', p2sh_multisig_cheltuit: 'p2sh', p2wsh_multisig_cheltuit: 'p2wsh', p2tr_cheltuit: 'p2tr', p2tr_nefinantat: 'p2tr' };
|
|
const tipuri = ROLURI_BTC.map((rol) => [rol, B.decodeazaIntrareBitcoin(A[rol]).type]);
|
|
cer('tipul de script derivat din octeti pentru cei 8 vectori Bitcoin', da(tipuri.every(([k, t]) => t === tipAsteptat[k])), tipuri.map(([k, t]) => k + '=' + t).join(' '));
|
|
cer('intrari rele numite: adresa trunchiata, p2pk cu cheie scurta, script gol', da(!!B.decodeazaIntrareBitcoin('1Pg8BtbzJF7Th2wgPM8twBxWePHDrQ6vM').error && !!B.decodeazaIntrareBitcoin('p2pk:02ab').error && !!B.decodeazaIntrareBitcoin('script:').error));
|
|
const el = JSON.parse(fs.readFileSync(path.join(FIX, 'electrum.json'), 'utf8'));
|
|
const brute = Object.entries(el.raspunsuri).filter(([k]) => k.startsWith('blockchain.transaction.get:'));
|
|
let refacute = 0, hashuite = 0;
|
|
for (const [k, hex] of brute) { const raw = Buffer.from(hex, 'hex'); const t = B.parseTx(raw); txs[t.txid] = t; if (B.serializeTx(t).equals(raw)) refacute++; if (t.txid === k.slice('blockchain.transaction.get:'.length)) hashuite++; }
|
|
cer(`toate cele ${brute.length} tranzactii reale din fixtura: refacute octet cu octet ${refacute}/${brute.length}, hashuite la id ${hashuite}/${brute.length}`, da(brute.length >= 30 && refacute === brute.length && hashuite === brute.length));
|
|
cer('parserul refuza octeti in plus la coada si o tranzactie fara intrari', da((() => { const h = brute[0][1]; try { B.parseTx(Buffer.from(h + '00', 'hex')); return false; } catch { } try { B.parseTx(Buffer.from('01000000' + '00' + '00' + '00000000', 'hex')); return false; } catch { return true; } })()));
|
|
// annex BIP-341: un singur element care incepe cu 0x50 e o semnatura, nu un annex
|
|
const tTr = B.decodeazaIntrareBitcoin(A.p2tr_nefinantat);
|
|
const sig50 = Buffer.concat([Buffer.from([0x50]), Buffer.alloc(63, 1)]);
|
|
cer('BIP-341: o semnatura de 64 de octeti care incepe cu 0x50, singura in witness, e o cheltuire pe calea de cheie (nu un annex)', da(B.dovezileIntrarii(tTr, { scriptSig: Buffer.alloc(0), witness: [sig50] }, true).some((f) => f.what === 'taproot-key-path-spend') && B.dovezileIntrarii(tTr, { scriptSig: Buffer.alloc(0), witness: [sig50, Buffer.from([0x50, 1])] }, true).some((f) => f.what === 'taproot-key-path-spend')));
|
|
// runda 7 (MEDIU-A): genezele celor cinci retele, recalculate AICI din campurile antetului de geneza (Bitcoin Core chainparams.cpp:
|
|
// versiunea 1, radacina merkle, ora, bits, nonce), trebuie sa fie exact hash-urile din tabelul uneltei (bitcoin.mjs RETELE_BTC), iar
|
|
// bits-ul fiecarei geneze trebuie sa stea sub limita de dovada de lucru pe care tabelul o da retelei ei (controlul limitelor)
|
|
await incearca('MEDIU-A: genezele Bitcoin (mainnet, testnet3, testnet4, signet, regtest) recalculate din campurile antetului == RETELE_BTC din unealta; bits-ul fiecarei geneze sub limita retelei ei, iar bits-ul signet / regtest PESTE limita mainnet', async () => {
|
|
const antet = (merkle, timp, bits, nonce) => { const b = Buffer.alloc(80); b.writeInt32LE(1, 0); Buffer.from(merkle, 'hex').reverse().copy(b, 36); b.writeUInt32LE(timp, 68); b.writeUInt32LE(bits, 72); b.writeUInt32LE(nonce >>> 0, 76); return b.toString('hex'); };
|
|
const M = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b';
|
|
const geneze = { mainnet: antet(M, 1231006505, 0x1d00ffff, 2083236893), testnet3: antet(M, 1296688602, 0x1d00ffff, 414098458), testnet4: antet('7aa0a7ae1e223414cb807e40cd57e667b718e42aaf9306db9102fe28912b7b4e', 1714777860, 0x1d00ffff, 393743547), signet: antet(M, 1598918400, 0x1e0377ae, 52613770), regtest: antet(M, 1296688602, 0x207fffff, 2) };
|
|
const rele = Object.entries(geneze).filter(([n, hx]) => { const a = B.antetBloc(hx); return a.eroare || a.hash !== B.RETELE_BTC[n].geneza || B.reteaDinGeneza(a.hash) !== n || B.tintaDinBits(a.bits) > B.RETELE_BTC[n].limita; }).map(([n]) => n);
|
|
const pesteMainnet = [0x1e0377ae, 0x207fffff].every((b) => B.tintaDinBits(b) > B.RETELE_BTC.mainnet.limita) && B.tintaDinBits(0x1d00ffff) <= B.RETELE_BTC.mainnet.limita;
|
|
return [rele.length === 0 && pesteMainnet && Object.keys(B.RETELE_BTC).length === 5 && B.reteaDinGeneza('00'.repeat(32)) === null, `geneze rele: ${rele.join(',') || 'niciuna'}; signet / regtest peste limita mainnet: ${pesteMainnet}`];
|
|
});
|
|
// runda 7 (MIC-D): iesirea P2TR fara arbore (BIP-86) a unei chei, pe vectorul din BIP-86 (contul 0, prima adresa de primire): cheia
|
|
// interna cc8a4bc6... da cheia de iesire a60869f0... si adresa bc1p5cyxnuxm...; un bit schimbat in cheia interna da alta cheie
|
|
await incearca('MIC-D: BIP-86, vectorul din BIP (cheia interna cc8a4bc6.. -> cheia de iesire a60869f0.., adresa bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr) refacut de cheieIesireBip86; o alta cheie interna da alta iesire', async () => {
|
|
const P = secp256k1.ProjectivePoint.fromHex('02cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115');
|
|
const q = B.cheieIesireBip86(P); const q2 = B.cheieIesireBip86(secp256k1.ProjectivePoint.fromHex('03cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115'));
|
|
const alta = B.cheieIesireBip86(secp256k1.ProjectivePoint.fromHex(Buffer.from(cheie('bip86-alta').pub)));
|
|
return [!!q && B.hex(q) === 'a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c' && B.segwitEncode('bc', 1, q) === 'bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr' && !!q2 && q2.equals(q) && !!alta && !alta.equals(q), `iesirea: ${q ? B.hex(q).slice(0, 16) : 'nimic'}..; aceeasi x cu y impar (BIP-340: x-only) da aceeasi iesire: ${!!q2 && q2.equals(q)}`];
|
|
});
|
|
await incearca('RLP: codificarea proprie == rlpEncode din verify-proof.mjs (publicat) pe 4 structuri', async () => { if (fara('VERIFY_PROOF')) return { nemasurat: fara('VERIFY_PROOF') }; const { rlpEncode } = await import(pathToFileURL(VERIFY_PROOF).href); const probe = [[], [Buffer.alloc(0)], [Buffer.from([1]), [Buffer.from('ab'.repeat(60), 'hex')]], Buffer.from('x'.repeat(1000))]; return probe.every((p) => E.rlpEncode(p).equals(rlpEncode(p))); });
|
|
// runda 8 (MEDIU-2 al reviziei rundei 7): punctele de control ale uneltei (bitcoin.mjs RETELE_BTC.mainnet.puncte) == pinii urmaritorului
|
|
// de antete al scutului (btc/antete.mjs PUNCTE_PINATE: inaltimea, hash-ul, cei 80 de octeti); fiecare antet se hashuieste la hash-ul lui,
|
|
// atinge dovada de lucru a campului lui bits si sta sub limita mainnet; punctDeControl alege cel mai inalt punct <= capatul lantului,
|
|
// da { sub } sub cel mai jos si null pe o retea fara puncte
|
|
await incearca('MEDIU-2 (r8): punctele de control ale uneltei == pinii lui btc/antete.mjs, fiecare antet hashuit la hash-ul lui si sub limita mainnet; punctDeControl: 971000 -> 969696, 969686 -> 967680, 967679 -> sub 967680, regtest -> niciunul', async () => {
|
|
if (fara('PINI')) return { nemasurat: fara('PINI') };
|
|
const { PUNCTE_PINATE } = await import(pathToFileURL(PINI).href);
|
|
const P = B.RETELE_BTC.mainnet.puncte || [];
|
|
const rele = P.filter((p) => { const q = PUNCTE_PINATE.find((x) => x.height === p.height); const a = B.antetBloc(p.header); return !q || q.hash !== p.hash || q.header !== p.header || a.eroare || a.hash !== p.hash || B.tintaDinBits(a.bits) > B.RETELE_BTC.mainnet.limita; }).map((p) => p.height);
|
|
const alege = [B.punctDeControl('mainnet', 971000), B.punctDeControl('mainnet', 969686), B.punctDeControl('mainnet', 967679), B.punctDeControl('regtest', 971000)];
|
|
return [P.length === 2 && rele.length === 0 && alege[0].height === 969696 && alege[1].height === 967680 && alege[2].sub === 967680 && alege[3] === null, `puncte ${P.map((p) => p.height).join(',')}; rele: ${rele.join(',') || 'niciunul'}; alegerea: ${JSON.stringify(alege.map((x) => x && (x.height || x)))}`];
|
|
});
|
|
// runda 8 (MEDIU-1): constructorul de trie al probei pe vectorii Ethereum (trie gol, "dogs", "puppy": radacinile cunoscute), iar
|
|
// plimbatorul uneltei (ethereum.mjs mptGet / contDinDovada) da acelasi rezultat ca plimbatorul PUBLICAT (verificator-falcon/mpt.mjs) pe
|
|
// dovezile fixturii, pe dovada unui cont absent; un nod schimbat, o alta radacina si o dovada goala nu duc la cont (eroare numita)
|
|
await incearca('MEDIU-1 (r8): trieDin pe vectorii Ethereum (gol 56e81f17.., dogs 8aad789d.., puppy 5991bb8c..); contDinDovada == mptGet publicat pe toate dovezile fixturii si pe o absenta; un nod schimbat / alta radacina / dovada goala -> eroare', async () => {
|
|
if (fara('MPT')) return { nemasurat: fara('MPT') };
|
|
const M = await import(pathToFileURL(MPT).href);
|
|
const t = (o) => E.trieDin(Object.entries(o).map(([k, v]) => ({ cheie: Buffer.from(k), valoare: Buffer.from(v) }))).radacina.toString('hex');
|
|
const vectori = t({}) === '56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421' && t({ doe: 'reindeer', dog: 'puppy', dogglesworth: 'cat' }) === '8aad789dff2f538bca5d8ea56e8abe10f4c7ba3a5dea95fea4cd6e7c3a1168d3' && t({ do: 'verb', horse: 'stallion', doge: 'coin', dog: 'puppy' }) === '5991bb8c6514148a29db676a14ac506cd2cd5775ace63c30a4fe457715e9ac84';
|
|
const fx = JSON.parse(fs.readFileSync(path.join(FIX, 'eth-rpc.json'), 'utf8')).raspunsuri; const cap = fx['eth_blockNumber:[]']; const radacina = fx[`eth_getBlockByNumber:${JSON.stringify([cap, false])}`].stateRoot;
|
|
const dovezi = Object.entries(fx).filter(([k]) => k.startsWith('eth_getProof:')).map(([k, v]) => [JSON.parse(k.slice('eth_getProof:'.length))[0], v]);
|
|
const absent = '0x' + '22'.repeat(20); const sAbs = E.stareConturi([{ address: ETHV.primitor.toLowerCase(), nonce: 0, code: '0x' }, { address: '0x' + '33'.repeat(20), nonce: 5, code: '0x' }]);
|
|
const laFel = (rad, adr, d) => { const a = E.contDinDovada(rad, adr, d); let p; try { const v = M.mptGet(rad, M.keccak(M.hexb(adr)), d.accountProof); p = v ? M.rlpDecode(v) : null; } catch { p = 'eroare'; } return a.eroare ? p === 'eroare' : (a.absent ? p === null : Array.isArray(p) && BigInt('0x' + (p[0].toString('hex') || '0')) === a.nonce && p[3].toString('hex') === a.codeHash); };
|
|
const toate = dovezi.every(([adr, d]) => laFel(radacina, adr, d)) && laFel(sAbs.stateRoot, absent, sAbs.dovada(absent)) && E.contDinDovada(sAbs.stateRoot, absent, sAbs.dovada(absent)).absent === true;
|
|
const [adr0, d0] = dovezi[0]; const rau = [...d0.accountProof]; const u = rau.length - 1; rau[u] = rau[u].slice(0, -2) + (rau[u].endsWith('00') ? '01' : '00');
|
|
const negative = [E.contDinDovada(radacina, adr0, { ...d0, accountProof: rau }), E.contDinDovada('0x' + '44'.repeat(32), adr0, d0), E.contDinDovada(radacina, adr0, { ...d0, accountProof: [] }), E.contDinDovada(radacina, adr0, { nonce: '0x0' })].map((x) => x.eroare || 'TRECUT');
|
|
return [vectori && dovezi.length === 4 && toate && negative.every((x) => x !== 'TRECUT'), `vectori ${vectori}; ${dovezi.length} dovezi, la fel cu plimbatorul publicat: ${toate}; negative: ${negative.map((x) => x.slice(0, 30)).join(' | ')}`];
|
|
});
|
|
// EIP-155, vectorul din EIP: nonce 9, gasPrice 20 gwei, gas 21000, to 0x3535.., value 1e18, data gol, chain 1, cheia 0x46..46
|
|
const k46 = Buffer.alloc(32, 0x46); const adr46 = E.adresaDinCheie(Buffer.from(secp256k1.getPublicKey(k46, false)));
|
|
const tx155 = { type: '0x0', nonce: '0x9', gasPrice: '0x4a817c800', gas: '0x5208', to: '0x3535353535353535353535353535353535353535', value: '0xde0b6b3a7640000', input: '0x', v: '0x25', r: '0x28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276', s: '0x67cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83' };
|
|
const h155 = E.hashuriTx(tx155);
|
|
cer('EIP-155: hash-ul de semnare al vectorului din EIP e daf5a779... si expeditorul recuperat e 0x9d8a62f6...', da(h155.signingHash.toString('hex') === 'daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53' && E.recupereazaExpeditor(tx155).address === '0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f'));
|
|
// tranzactii 0x1 si 0x3 semnate aici cu cheia 0x46..46 si recuperate
|
|
const semneaza = (tx) => { const h = E.hashuriTx(tx); const s = secp256k1.sign(h.signingHash, k46); return { ...tx, yParity: '0x' + s.recovery.toString(16), r: '0x' + s.r.toString(16), s: '0x' + s.s.toString(16) }; };
|
|
const baza = { chainId: '0x1', nonce: '0x1', gas: '0x5208', to: '0x3535353535353535353535353535353535353535', value: '0x1', input: '0x', accessList: [{ address: '0x3535353535353535353535353535353535353535', storageKeys: ['0x' + '01'.repeat(32)] }], yParity: '0x0', r: '0x1', s: '0x1' };
|
|
const t1 = semneaza({ ...baza, type: '0x1', gasPrice: '0x1' });
|
|
const t3 = semneaza({ ...baza, type: '0x3', maxPriorityFeePerGas: '0x1', maxFeePerGas: '0x2', maxFeePerBlobGas: '0x1', blobVersionedHashes: ['0x01' + '00'.repeat(31)] });
|
|
const rec = (t) => { const r = E.recupereazaExpeditor({ ...t, hash: E.hashuriTx(t).txHash }); return r.ok && r.address === adr46 && r.txHashMatches; };
|
|
cer('tranzactii 0x1 (EIP-2930) si 0x3 (EIP-4844) semnate cu cheia de proba se recupereaza la adresa ei', da(rec(t1) && rec(t3)));
|
|
const ethFix = JSON.parse(fs.readFileSync(path.join(FIX, 'eth-rpc.json'), 'utf8'));
|
|
const blocKey = Object.keys(ethFix.raspunsuri).find((k) => k.startsWith('eth_getBlockByNumber:')); const bloc = ethFix.raspunsuri[blocKey];
|
|
let recup = 0; const tipuriTx = {};
|
|
for (const tx of bloc.transactions) { const r = E.recupereazaExpeditor(tx); tipuriTx[tx.type] = (tipuriTx[tx.type] || 0) + 1; if (r.ok && r.address === tx.from.toLowerCase() && r.txHashMatches) recup++; }
|
|
cer(`Ethereum: cheia recuperata si hash-ul refacut pe TOATE cele ${bloc.transactions.length} tranzactii ale blocului din fixtura`, da(recup === bloc.transactions.length && bloc.transactions.length >= 50), JSON.stringify(tipuriTx));
|
|
const tx0 = bloc.transactions.find((t) => t.from.toLowerCase() === ETHV.expeditor.toLowerCase());
|
|
const txRau = { ...tx0, r: '0x' + (BigInt(tx0.r) ^ 1n).toString(16).padStart(64, '0') }; const rRau = E.recupereazaExpeditor(txRau);
|
|
cer('Ethereum negativ: un bit schimbat in r -> alta adresa sau niciuna, niciodata expeditorul', da(!(rRau.ok && rRau.address === tx0.from.toLowerCase())), rRau.ok ? 'a dat ' + rRau.address.slice(0, 12) + '..' : rRau.reason);
|
|
const tx4 = bloc.transactions.find((t) => t.type === '0x4' && Array.isArray(t.authorizationList) && t.authorizationList.length);
|
|
const aut = tx4 ? E.recupereazaAutoritate(tx4.authorizationList[0]) : { ok: false, reason: 'nicio tranzactie 0x4 in bloc' };
|
|
cer('EIP-7702: autoritatea recuperata din autorizatia reala a blocului e adresa din alese.json, iar un bit schimbat in r nu o mai da', da(aut.ok && aut.address === ETHV.autoritate7702.toLowerCase() && !(E.recupereazaAutoritate({ ...tx4.authorizationList[0], r: '0x' + (BigInt(tx4.authorizationList[0].r) ^ 1n).toString(16).padStart(64, '0') }).address === aut.address)), aut.ok ? '' : aut.reason);
|
|
cer('EIP-7702: designatorul 0xef0100||adresa e recunoscut, un cod de contract nu', da(E.esteDelegare7702('0xef0100' + '11'.repeat(20)) && !E.esteDelegare7702('0x6060604052') && !E.esteDelegare7702('0xef0100' + '11'.repeat(19))));
|
|
cer('EIP-55: suma valida, minuscule acceptate ca neverificate, suma gresita REFUZATA', da(E.decodeazaAdresaEth('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2').checksum === 'valid' && E.decodeazaAdresaEth(ETHV.primitor).checksum === 'none' && !!E.decodeazaAdresaEth('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc3').error));
|
|
});
|
|
|
|
// ================================================================ 2. fixturi
|
|
await pas('2. fixturile', async () => {
|
|
for (const f of ['esplora', 'electrum', 'bitcoind', 'eth-rpc']) { const j = JSON.parse(fs.readFileSync(path.join(FIX, f + '.json'), 'utf8')); cer(`${f}.json: sursa si data inregistrarii scrise in fisier, fara CR`, da(j._sursa && /^\d{4}-\d{2}-\d{2}T/.test(j._sursa.cand) && !!j._sursa.baza && !fs.readFileSync(path.join(FIX, f + '.json')).includes(0x0d)), j._sursa ? j._sursa.cand : ''); }
|
|
// runda 8: starea Ethereum a fixturii (SINTETICA, scris in _sursa.nota) e coerenta: fiecare dovada duce de la radacina de stare a
|
|
// capului la contul ei, iar codul servit la numarul capului se hashuieste la hash-ul de cod dovedit; punctele de control servite de
|
|
// fixturile Bitcoin sunt cele ale uneltei
|
|
{
|
|
const fx = JSON.parse(fs.readFileSync(path.join(FIX, 'eth-rpc.json'), 'utf8')).raspunsuri; const cap = fx['eth_blockNumber:[]']; const capB = fx[`eth_getBlockByNumber:${JSON.stringify([cap, false])}`];
|
|
const conturi = Object.entries(fx).filter(([k]) => k.startsWith('eth_getProof:')).map(([k, v]) => { const adr = JSON.parse(k.slice('eth_getProof:'.length))[0]; const c = E.contDinDovada(capB && capB.stateRoot, adr, v); const cod = fx['eth_getCode:' + JSON.stringify([adr, cap])]; return { adr, c, codOk: c.eroare ? false : c.codeHash === E.HASH_COD_GOL ? cod === undefined : typeof cod === 'string' && E.keccak(Buffer.from(cod.slice(2), 'hex')).toString('hex') === c.codeHash }; });
|
|
cer('eth-rpc.json (r8): fiecare dovada eth_getProof duce la radacina de stare a capului si codul servit la numarul capului se hashuieste la hash-ul de cod dovedit; nicio cerere la \'latest\' nu mai e in fixtura', da(conturi.length === 4 && conturi.every((x) => !x.c.eroare && x.codOk) && !Object.keys(fx).some((k) => k.includes('"latest"'))), conturi.map((x) => `${x.adr.slice(0, 8)}: ${x.c.eroare || 'nonce ' + x.c.nonce}${x.codOk ? '' : ' COD RAU'}`).join('; '));
|
|
const el = JSON.parse(fs.readFileSync(path.join(FIX, 'electrum.json'), 'utf8')).raspunsuri, es = JSON.parse(fs.readFileSync(path.join(FIX, 'esplora.json'), 'utf8')).raspunsuri, bc = JSON.parse(fs.readFileSync(path.join(FIX, 'bitcoind.json'), 'utf8')).raspunsuri;
|
|
cer('electrum / esplora / bitcoind.json (r8): blocurile de la inaltimile punctelor de control sunt punctele uneltei; geneza si inaltimea Core sunt ale mainnet-ului si ale varfului fixturilor (971000)', da(B.RETELE_BTC.mainnet.puncte.every((p) => el['blockchain.block.header:' + p.height] === p.header && es['/block-height/' + p.height] === p.hash && bc['getblockhash:' + p.height] === p.hash) && bc['getblockhash:0'] === B.RETELE_BTC.mainnet.geneza && bc.getblockcount === 971000));
|
|
}
|
|
cer('alese.json: fiecare rol are provenienta scrisa, si vectori.txt contine exact adresele rolurilor', da(ROLURI_BTC.every((r) => ALESE.provenienta && ALESE.provenienta[r]) && ['expeditor', 'primitor', 'contract', 'autoritate7702'].every((r) => ALESE.provenienta['ethereum_' + r]) && [...ROLURI_BTC.map((r) => A[r]), ETHV.expeditor, ETHV.primitor, ETHV.contract, ETHV.autoritate7702].every((a) => vectori.includes(a)) && vectori.length === 12));
|
|
});
|
|
|
|
// ================================================================ 3. modul 1
|
|
await pas('3. modul 1 (offline) sub paza de retea', async () => {
|
|
m1 = ruleaza({ args: ['--mod', '1', '--adrese', LISTA_TOT, '--xpub-partajat', 'nu', '--semnaturi-offchain', 'necunoscut', '--alte-lanturi', 'da'] });
|
|
cer('modul 1 scrie raportul si iese 0', da(m1.cod === 0 && m1.scris), m1.err.slice(0, 120));
|
|
if (!m1.raport) return;
|
|
asteapta(m1.raport, ASTEPT_MOD1, 'modul 1');
|
|
cer('modul 1: paza incarcata, ZERO conexiuni / DNS / UDP / workeri / procese copil', da(!!m1.jurnal && m1.jurnal.toate.length === 1), m1.jurnal ? `${m1.jurnal.toate.length - 1} evenimente` : 'jurnal lipsa');
|
|
cerVerif('modul 1: plicul compliance e VALID la verificatorul de referinta AIP-23', da(verdictVerificator(m1.plic) === 'VALID'));
|
|
const plic1 = JSON.parse(fs.readFileSync(m1.plic, 'utf8'));
|
|
cer('modul 1: evidenceHash din plic == amprenta canonica a raport.json (verificaRaport din raport-conformitate)', da(verificaRaport(m1.raport, plic1).ok && sha256Canonic(m1.raport) === plic1.statement.evidenceHash));
|
|
cer('modul 1: Markdown-ul pune cele doua intrebari si consemneaza raspunsurile ca DECLARATII', da(/extended public key/.test(m1.md) && /off chain/.test(m1.md) && /Declared answer: \*\*no \(declared\)\*\*/.test(m1.md) && /Declared answer: \*\*unknown \(declared\)\*\*/.test(m1.md) && /not a measurement/.test(m1.md)));
|
|
cer('modul 1: termenele citate cu document, versiune si data (NIST IR 8547 ipd 2024-11-12: 2030/2035; UE 2025-06-23: 2030/2035)', da(/NIST IR 8547 ipd/.test(m1.md) && /initial public draft/.test(m1.md) && /2024-11-12/.test(m1.md) && /deprecated after 2030, disallowed after 2035/.test(m1.md) && /2025-06-23/.test(m1.md) && /2030-12-31/.test(m1.md) && /2035-12-31/.test(m1.md) && m1.raport.deadlines.regimes['nist-ir-8547'].read === '2026-09-28'));
|
|
cer('modul 1: raportul spune ce NU vede (cheltuirile) la fiecare adresa de tip hash', da(m1.raport.addresses.filter((a) => a.chain === 'bitcoin' && a.label === F_).every((a) => a.notSeen.some((n) => /spends from this address/.test(n)))));
|
|
cer('modul 1: zero cuvinte interzise in JSON si Markdown', da(verificaLimbaj(JSON.stringify(m1.raport)).length === 0 && verificaLimbaj(m1.md).length === 0));
|
|
// MIC-1 (runda 6): a treia intrebare (aceeasi cheie pe alt lant) e pusa si consemnata; fiecare F numeste celelalte lanturi, fiecare F Bitcoin
|
|
// randul N8 (aceeasi cheie sub alt tip de script), fiecare F Ethereum semnaturile off-chain
|
|
const fM1 = m1.raport.addresses.filter((a) => a.label === F_);
|
|
cer('modul 1 (MIC-1): a treia intrebare (aceeasi cheie pe ALT lant) e pusa si consemnata; fiecare F numeste celelalte lanturi ("no chain was read"), fiecare F Bitcoin randul N8, fiecare F Ethereum semnaturile off-chain', da(/the same key, or the same address, signed on another chain/.test(m1.md) && m1.raport.declarations.otherChains && m1.raport.declarations.otherChains.answer === 'yes (declared)' && /Declared answer: \*\*yes \(declared\)\*\*/.test(m1.md) && fM1.length >= 6 && fM1.every((a) => a.notSeen.some((n) => /^other chains sharing this key: .*no chain was read \(mode 1\)/.test(n))) && fM1.filter((a) => a.chain === 'bitcoin').every((a) => a.notSeen.some((n) => /^the same key under another script type/.test(n))) && fM1.filter((a) => a.chain === 'ethereum').every((a) => a.notSeen.some((n) => /^keys disclosed off chain \(EIP-191/.test(n)))), `${fM1.length} F`);
|
|
cer('modul 1: nimic despre proprietar: subiectul e derivat din adrese, nu un nume', da(/^addresses:[0-9a-f]{16}$/.test(m1.raport.subject)));
|
|
});
|
|
|
|
// ================================================================ 4. modul 2
|
|
await pas('4. modul 2 prin machete (raspunsuri inregistrate), sub paza', async () => {
|
|
const txids = Object.keys(bitcoindFix.raspunsuri).filter((k) => k.startsWith('getrawtransaction:')).map((k) => k.slice('getrawtransaction:'.length));
|
|
const LISTA_TXIDS = scrieLista('txids', txids);
|
|
for (const [fel, fixtura, flag, extra] of [['electrum', 'electrum.json', '--electrum', []], ['esplora', 'esplora.json', '--esplora', []], ['bitcoind', 'bitcoind.json', '--bitcoind', ['--txids', LISTA_TXIDS]]]) {
|
|
const m = await porneste(fel, path.join(FIX, fixtura));
|
|
const r = ruleaza({ args: ['--mod', '2', '--adrese', LISTA_BTC, flag, fel === 'electrum' ? m.tinta : m.url, ...extra], tinte: m.tinta });
|
|
await sleep(150); const c = m.contor(); m.opreste();
|
|
const codAsteptat = fel === 'bitcoind' ? 2 : 0;
|
|
cer(`${fel}: raportul e scris si iese ${codAsteptat}`, da(r.cod === codAsteptat && r.scris), r.err.slice(0, 120));
|
|
if (!r.raport) continue;
|
|
asteapta(r.raport, fel === 'bitcoind' ? ASTEPT_CORE_TX : ASTEPT_BTC, fel);
|
|
if (fel !== 'bitcoind') { const fR = r.raport.addresses.filter((a) => a.label === F_); cer(`${fel} (MEDIU-1, MEDIU-2, MIC-1): fiecare verdict numeste pana unde merge istoricul ("history served up to height 971000 (tip header time 2026-10-11T22:53:11Z)"); fiecare F (${fR.length}) numeste randul N8 si celelalte lanturi, iar P2PKH-ul necheltuit si surorile citite (P2WPKH, P2SH-P2WPKH)`, da(r.raport.addresses.every((a) => a.evidence[0] === 'history served up to height 971000 (tip header time 2026-10-11T22:53:11Z)') && fR.length >= 2 && fR.every((a) => a.notSeen.some((n) => /^the same key under another script type that cannot be derived/.test(n)) && a.notSeen.some((n) => /^other chains sharing this key: .*only the Bitcoin chain served by the target was read/.test(n))) && dovada(r.raport, A.p2pkh_necheltuit, 'sibling scripts of the same HASH160 read through the same target: P2WPKH bc1q') && dovada(r.raport, A.p2pkh_necheltuit, ', P2SH-P2WPKH 3')), (gaseste(r.raport, A.p2pkh_necheltuit).evidence.find((e) => /^sibling/.test(e)) || '').slice(0, 120)); }
|
|
if (fel === 'bitcoind' && !P2TR_NEF_ARE_IESIRI) cer('bitcoind cu --txids: P2TR-ul pe care nicio tranzactie data nu il atinge (si fara iesiri necheltuite) iese NEMASURAT numit, nu F "no output exists yet"', da(nemasuratCu(r.raport, A.p2tr_nefinantat, `none of the ${txids.length} transaction(s) given with --txids touches this address`)), detaliu(r.raport, A.p2tr_nefinantat).slice(0, 120));
|
|
cer(`${fel}: dovezile numesc ce s-a vazut (cheia in scriptSig, cheia in witness, m-of-n in redeem/witness script, cheltuirea P2TR, iesirea P2PK)`, da(dovada(r.raport, A.p2pkh_cheltuit, 'public key revealed in the scriptSig') && dovada(r.raport, A.p2wpkh_cheltuit, 'public key revealed in the witness') && gaseste(r.raport, A.p2sh_multisig_cheltuit).evidence.some((e) => /\d+-of-\d+ multisig/.test(e)) && gaseste(r.raport, A.p2wsh_multisig_cheltuit).evidence.some((e) => /\d+-of-\d+ multisig/.test(e)) && dovada(r.raport, A.p2tr_cheltuit, 'spend in') && dovada(r.raport, A.p2pk, 'P2PK output')), [A.p2sh_multisig_cheltuit, A.p2wsh_multisig_cheltuit, A.p2tr_cheltuit].map((a) => detaliu(r.raport, a)).join(' | '));
|
|
if (fel === 'bitcoind') cer('bitcoind cu --txids: adresa P2PKH niciodata cheltuita iese NEMASURAT numit ("cannot check that it lists every transaction"), nu F: istoricul ei e lista data, nu o masuratoare', da(eticheta(r.raport, A.p2pkh_necheltuit) === U_ && nemasuratCu(r.raport, A.p2pkh_necheltuit, NU_E_COMPLETA)), detaliu(r.raport, A.p2pkh_necheltuit).slice(0, 120));
|
|
else cer(`${fel}: adresa P2PKH niciodata cheltuita spune ce a vazut (iesiri, nicio cheltuire) si ce nu vede (off chain)`, da(dovada(r.raport, A.p2pkh_necheltuit, 'no spend of this address') && nevazut(r.raport, A.p2pkh_necheltuit, /off chain/)));
|
|
cer(`${fel}: jurnalul pazei == conexiunile ACCEPTATE de macheta (${r.jurnal ? r.jurnal.conexiuni.length : '?'} == ${c.connections}), toate spre tinta declarata, zero DNS / procese`, da(!!r.jurnal && r.jurnal.conexiuni.length === c.connections && c.connections > 0 && r.jurnal.nepermise.length === 0 && r.jurnal.dns.length === 0 && r.jurnal.child.length === 0 && r.jurnal.worker.length === 0 && r.jurnal.conexiuni.every((x) => x.host === '127.0.0.1' && x.port === m.port)), `cereri servite ${c.requests}, necunoscute ${c.unknown.length}`);
|
|
cerVerif(`${fel}: plicul VALID la verificatorul de referinta; zero cuvinte interzise`, da(verdictVerificator(r.plic) === 'VALID' && verificaLimbaj(r.md).length === 0 && verificaLimbaj(JSON.stringify(r.raport)).length === 0));
|
|
if (fel === 'bitcoind') cer('bitcoind cu --txids: raportul spune ca nu exista index de adrese si ca s-au citit doar id-urile date', da(r.raport.addresses.every((a) => a.unmeasured || a.notSeen.some((n) => /no address index/.test(n)))));
|
|
if (fel === 'bitcoind') cer(`bitcoind cu --txids: lantul nodului (geneza, inaltimea, punctul de control: 3 cereri) si tranzactiile date se citesc O DATA pentru tot raportul (cereri servite ${c.requests} == 3 + 8 scanuri + ${txids.length} txids), pozitia e hash-ul INTREG al blocului (nu un prefix de zerouri, nu "confirmations")`, da(c.requests === 3 + 8 + txids.length && r.raport.adapterNotes.some((n) => /the chain the node serves was read once/.test(n)) && r.raport.adapterNotes.some((n) => /given with --txids were read once/.test(n)) && gaseste(r.raport, A.p2pkh_cheltuit).evidence.some((e) => /block [0-9a-f]{64}\)/.test(e)) && !JSON.stringify(r.raport).includes('confirmations')), (gaseste(r.raport, A.p2pkh_cheltuit).evidence.find((e) => /block /.test(e)) || '').slice(-90));
|
|
}
|
|
// Bitcoin Core FARA --txids: scanul vede numai iesirile necheltuite; nicio adresa de tip hash nu are voie sa iasa F
|
|
{
|
|
const m = await porneste('bitcoind', path.join(FIX, 'bitcoind.json'));
|
|
const r = ruleaza({ args: ['--mod', '2', '--adrese', LISTA_BTC, '--bitcoind', m.url], tinte: m.tinta }); m.opreste();
|
|
cer(`bitcoind FARA --txids: adresele de tip hash si scripturile fara iesiri necheltuite ies NEMASURAT numit ("no address index"), cele cu iesiri necheltuite si cheia in script ies E; zero F; cod 2`, da(r.cod === 2 && predicatCoreFaraTxids(r)), r.raport ? compara(r.raport, ASTEPT_CORE_FARA_TXIDS).rele || ROLURI_BTC.map((rol) => rol + '=' + eticheta(r.raport, A[rol])).join(' ') : r.err.slice(0, 100));
|
|
}
|
|
{
|
|
const m = await porneste('eth', path.join(FIX, 'eth-rpc.json'));
|
|
const r = ruleaza({ args: ['--mod', '2', '--adrese', LISTA_ETH, '--eth-rpc', m.url, '--eth-blocks', `${ETHV.bloc}..${ETHV.bloc}`], tinte: m.tinta });
|
|
await sleep(150); const c = m.contor(); m.opreste();
|
|
cer('eth-rpc: raportul e scris si iese 0', da(r.cod === 0 && r.scris), r.err.slice(0, 120));
|
|
if (!r.raport) return;
|
|
asteapta(r.raport, ASTEPT_ETH, 'eth-rpc');
|
|
cer('eth-rpc: expeditorul e expus prin CHEIA RECUPERATA din semnatura (nu prin campul from), contractul si contul fara iesiri spun de ce', da(dovada(r.raport, ETHV.expeditor, 'public key recovered from the signature of transaction') && dovada(r.raport, ETHV.contract, 'contract account') && dovada(r.raport, ETHV.primitor, 'no code and nonce 0 at head block 26172585, both proven under its state root')));
|
|
cer('eth-rpc: autoritatea EIP-7702 reala e expusa prin designatorul din cod SI prin autorizatia gasita in bloc (autoritatea recuperata din semnatura ei)', da(dovada(r.raport, ETHV.autoritate7702, 'EIP-7702 delegation designator') && dovada(r.raport, ETHV.autoritate7702, 'authorization signed by this account in transaction ' + ETHV.tx7702)), detaliu(r.raport, ETHV.autoritate7702));
|
|
cer('eth-rpc (r8): raportul spune ce leaga dovada de stare (nonce-ul si codul, de radacina de stare a capului) si ce NU leaga (capul, de consensul lantului)', da(nevazut(r.raport, ETHV.primitor, /^the head block itself: .*does not check that block against the consensus of the chain/)));
|
|
cer('eth-rpc (MIC-1): fiecare F (contul fara iesiri, contractul) numeste semnaturile off-chain si celelalte lanturi ("only chain id 1 was read")', da([ETHV.primitor, ETHV.contract].every((a) => eticheta(r.raport, a) === F_ && nevazut(r.raport, a, /^keys disclosed off chain \(EIP-191/) && nevazut(r.raport, a, /^other chains sharing this key: every EVM chain .*only chain id 1 was read/))));
|
|
cer(`eth-rpc: jurnalul pazei == conexiunile acceptate de macheta (${r.jurnal ? r.jurnal.conexiuni.length : '?'} == ${c.connections}), toate spre tinta`, da(!!r.jurnal && r.jurnal.conexiuni.length === c.connections && c.connections > 0 && r.jurnal.nepermise.length === 0));
|
|
cerVerif('eth-rpc: plicul VALID; zero cuvinte interzise', da(verdictVerificator(r.plic) === 'VALID' && verificaLimbaj(r.md).length === 0));
|
|
const m2 = await porneste('eth', path.join(FIX, 'eth-rpc.json'));
|
|
const r2 = ruleaza({ args: ['--mod', '2', '--adrese', LISTA_ETH, '--eth-rpc', m2.url, '--eth-blocks', `${ETHV.bloc + 1000000}..${ETHV.bloc + 1000009}`], tinte: m2.tinta }); m2.opreste();
|
|
cer('eth-rpc: o fereastra --eth-blocks care incepe DUPA varf e REFUZATA (cod 1, nimic scris), nu o eticheta F pe nonce 0 cu "0 block(s) read"', da(r2.cod === 1 && !r2.scris && /REFUSED \(eth-blocks\)/.test(r2.err) && /after the head/.test(r2.err)), r2.err.slice(0, 120));
|
|
}
|
|
});
|
|
|
|
// ================================================================ 5. controale negative
|
|
await pas('5. controalele negative', async () => {
|
|
// a. furnizor strain pe fiecare drum
|
|
await incearca('a. cate un furnizor strain pe fiecare drum numit de paza (fetch, dgram send, dns.promises.resolveTxt, dns.Resolver, dns.promises.Resolver, dns.lookupService, dns.promises.lookupService, dns.promises.reverse pe un literal, process.binding(tcp_wrap), process.dlopen, Worker, Worker luat prin reflectie, child_process, clasa ChildProcess, process.execve, o teava cu NUME DE SERVER pe loopback) plantat intr-o copie: TOATE PRINSE si refuzate inainte de orice pachet', async () => {
|
|
const d = copie('strain');
|
|
// un Worker luat prin reflectie (prototipul clasei, constructorul prototipului) care a PORNIT lasa un semn intr-un fisier: semnul nu are voie sa apara
|
|
const semn = path.join(T, 'semn-worker-reflectie.txt');
|
|
const teava = "process.platform === 'win32' ? [92, 92].map((c) => String.fromCharCode(c)).join('') + '127.0.0.1' + String.fromCharCode(92) + 'pipe' + String.fromCharCode(92) + 'aere-paza-proba-' + process.pid : '//127.0.0.1/aere-paza-proba-' + process.pid";
|
|
// lookupService pe 127.0.0.1: un literal NU e permis pentru lookupService (e o interogare getnameinfo), deci paza trebuie sa-l refuze;
|
|
// tinta e loopback, nu o adresa straina, ca o oglinda care scoate paza de pe acest drum sa nu ceara numele unei adrese din afara
|
|
// (ce face resolverul masinii cu o cerere getnameinfo pentru loopback e NEMASURAT)
|
|
const planta = [
|
|
"try { await fetch('http://198.51.100.9/', { signal: AbortSignal.timeout(3000) }); } catch {}",
|
|
"try { const dg = await import('node:dgram'); const s = dg.createSocket('udp4'); s.on('error', () => {}); await new Promise((res) => s.send(Buffer.from('aere'), 53, '198.51.100.9', () => res())); s.close(); } catch {}",
|
|
"try { const dnsP = await import('node:dns'); await dnsP.promises.resolveTxt('aere-paza-proba.198-51-100-9.invalid'); } catch {}",
|
|
"try { const dnsP = await import('node:dns'); await new Promise((res, rej) => new dnsP.Resolver().resolve4('aere-paza-proba2.198-51-100-9.invalid', (e, a) => (e ? rej(e) : res(a)))); } catch {}",
|
|
"try { const dnsP = await import('node:dns'); await new dnsP.promises.Resolver().resolve4('aere-paza-proba3.198-51-100-9.invalid'); } catch {}",
|
|
"try { const dnsP = await import('node:dns'); await new Promise((res, rej) => dnsP.lookupService('127.0.0.1', 9, (e, a) => (e ? rej(e) : res(a)))); } catch {}",
|
|
"try { const dnsP = await import('node:dns'); await dnsP.promises.lookupService('127.0.0.1', 7); } catch {}",
|
|
"try { const dnsP = await import('node:dns'); await dnsP.promises.reverse('198.51.100.9'); } catch {}",
|
|
"try { process.binding('tcp_wrap'); } catch {}",
|
|
"try { process.dlopen({ exports: {} }, 'aere-paza-addon-inexistent.node'); } catch {}",
|
|
"try { const wtP = await import('node:worker_threads'); const w = new wtP.Worker('setTimeout(() => {}, 10)', { eval: true }); await new Promise((res) => w.on('exit', res)); } catch {}",
|
|
"try { const cpP = await import('node:child_process'); cpP.spawnSync(process.execPath, ['-e', '0']); } catch {}",
|
|
// clasa ChildProcess, fara functiile modulului, cu mediu propriu (fara NODE_OPTIONS): pe aici pleaca orice spawn asincron
|
|
"try { const cpP = await import('node:child_process'); const c = new cpP.ChildProcess(); c.on('error', () => {}); c.spawn({ file: process.execPath, args: [process.execPath, '-e', '0'], envPairs: [], stdio: 'ignore' }); } catch {}",
|
|
// process.execve: pe Windows arunca ERR_FEATURE_UNAVAILABLE_ON_PLATFORM, dar paza il jurnalizeaza si il refuza INAINTE de apel
|
|
"try { process.execve(process.execPath, [process.execPath, '-e', '0'], {}); } catch {}",
|
|
// Worker-ul original cautat prin reflectie: prototipul clasei si constructorul prototipului
|
|
`try { const wtP = await import('node:worker_threads'); const fsP = await import('node:fs'); for (const W of [Object.getPrototypeOf(wtP.Worker), wtP.Worker.prototype.constructor]) { try { const w = new W('setTimeout(() => {}, 10)', { eval: true }); if (w && typeof w.threadId === 'number' && w.threadId > 0) { fsP.appendFileSync(${JSON.stringify(semn)}, 'worker-pornit;'); w.terminate(); } } catch {} } } catch {}`,
|
|
// o teava cu NUME DE SERVER (pe Windows o cale UNC, pe care sistemul o deschide prin SMB); serverul e loopback, deci nimic nu iese
|
|
`try { const netP = await import('node:net'); await new Promise((res) => { const s = netP.connect(${teava}); s.on('error', () => res()); s.on('connect', () => { s.destroy(); res(); }); setTimeout(res, 3000); }); } catch {}`,
|
|
].join('\n ');
|
|
const n = planteaza(path.join(d, 'raport.mjs'), '/*PLANTA:furnizor-strain*/', planta);
|
|
if (n !== 1) return `marcajul apare de ${n} ori, nu o data`;
|
|
const r = ruleaza({ dir: d, args: ['--mod', '1', '--adrese', LISTA_TOT] });
|
|
if (!r.jurnal) return 'jurnal lipsa';
|
|
const ref = r.jurnal.nepermise;
|
|
const dnsM = (m, h) => ref.some((x) => x.ev === 'dns' && x.method === m && (!h || x.host === h));
|
|
const copil = (m) => ref.some((x) => x.ev === 'child' && x.method === m);
|
|
const are = { connect: ref.some((x) => x.ev === 'connect' && x.host === '198.51.100.9'), udp: ref.some((x) => x.ev === 'udp' && x.host === '198.51.100.9' && x.port === 53), txt: dnsM('dns.promises.resolveTxt'), resolver: dnsM('Resolver.resolve4'), resolverP: dnsM('promises.Resolver.resolve4'), lookupService: dnsM('dns.lookupService', '127.0.0.1'), lookupServiceP: dnsM('dns.promises.lookupService', '127.0.0.1'), reverseLiteral: dnsM('dns.promises.reverse', '198.51.100.9'), binding: ref.some((x) => x.ev === 'binding' && x.name === 'tcp_wrap'), addon: ref.some((x) => x.ev === 'addon' && /aere-paza-addon-inexistent/.test(x.file)), worker: ref.some((x) => x.ev === 'worker'), workerReflectie: !fs.existsSync(semn) && ref.filter((x) => x.ev === 'worker').length >= 2, child: copil('spawnSync'), childClasa: copil('ChildProcess.spawn'), execve: copil('execve'), teavaServer: ref.some((x) => x.ev === 'connect' && typeof x.path === 'string' && /aere-paza-proba-/.test(x.path)) };
|
|
return [Object.values(are).every(Boolean) && r.jurnal.toate.filter((x) => x.ev !== 'incarcat').every((x) => x.allowed === false), Object.entries(are).filter(([, v]) => !v).map(([k]) => k + '=SCAPAT').join(' ') + ` (refuzate ${ref.length} pe ${Object.keys(are).length} drumuri, cod ${r.cod})`];
|
|
});
|
|
// fara paza se ruleaza unealta NEPLANTATA: copia plantata ar trimite, fara paza, pachete reale (DNS, TCP spre o adresa de documentatie)
|
|
await incearca('a. unealta FARA paza: niciun jurnal, deci citirea spune NEMASURAT (nu "zero conexiuni")', async () => { const r2 = ruleaza({ args: ['--mod', '1', '--adrese', LISTA_TOT], paza: false, mod: 'jurnal' }); return [r2.jurnal === null && r2.cod === 0, `cod ${r2.cod}`]; });
|
|
await incearca('a. modul JURNAL, numai tinte locale: un Worker si un copil Node (mediu mostenit) care se conecteaza la 127.0.0.1:1 au conexiunea LOR in jurnal (paza se incarca in ei prin --require); un socket UDP CONECTAT la 127.0.0.1:9 e jurnalizat cu adresa lui; o conexiune cu `lookup` propriu are in jurnal adresa intoarsa de lookup', async () => {
|
|
const d = copie('jurnal');
|
|
const cod = "const net = require('node:net'); const s = net.connect(1, '127.0.0.1'); s.on('error', () => {}); setTimeout(() => {}, 300);";
|
|
const planta = [
|
|
`try { const wtP = await import('node:worker_threads'); const w = new wtP.Worker(${JSON.stringify(cod + " setTimeout(() => process.exit(0), 400);")}, { eval: true }); await new Promise((res) => w.on('exit', res)); } catch {}`,
|
|
`try { const cpP = await import('node:child_process'); cpP.spawnSync(process.execPath, ['-e', ${JSON.stringify(cod)}], { timeout: 8000 }); } catch {}`,
|
|
"try { const dg = await import('node:dgram'); const s = dg.createSocket('udp4'); s.on('error', () => {}); await new Promise((res) => s.connect(9, '127.0.0.1', res)); await new Promise((res) => s.send(Buffer.from('aere'), () => res())); s.close(); } catch {}",
|
|
"try { const netP = await import('node:net'); await new Promise((res) => { const s = netP.connect({ host: 'aere-paza-lookup.invalid', port: 1, lookup: (h, o, cb) => (o && o.all ? cb(null, [{ address: '127.0.0.1', family: 4 }]) : cb(null, '127.0.0.1', 4)) }); s.on('error', () => res()); s.on('connect', () => { s.destroy(); res(); }); setTimeout(res, 3000); }); } catch {}",
|
|
].join('\n ');
|
|
const n = planteaza(path.join(d, 'raport.mjs'), '/*PLANTA:furnizor-strain*/', planta);
|
|
if (n !== 1) return `marcajul apare de ${n} ori, nu o data`;
|
|
const r = ruleaza({ dir: d, args: ['--mod', '1', '--adrese', LISTA_TOT], mod: 'jurnal' });
|
|
if (!r.jurnal) return 'jurnal lipsa';
|
|
const pidRaport = r.jurnal.toate[0].pid;
|
|
const conexiuni = r.jurnal.conexiuni.filter((x) => x.host === '127.0.0.1' && x.port === 1);
|
|
const dinWorker = conexiuni.some((x) => x.pid === pidRaport && x.tid !== 0), dinCopil = conexiuni.some((x) => x.pid !== pidRaport);
|
|
const incarcari = r.jurnal.toate.filter((x) => x.ev === 'incarcat').length;
|
|
const udp = r.jurnal.udp.some((x) => x.host === '127.0.0.1' && x.port === 9);
|
|
const rezolvat = r.jurnal.toate.some((x) => x.ev === 'resolved' && x.host === 'aere-paza-lookup.invalid' && x.address === '127.0.0.1');
|
|
// numai tinte locale: nimic din modul jurnal nu are voie sa iasa din masina; singurul DNS admis e dns.lookup pe un literal IP (bind-ul
|
|
// si connect-ul socketului UDP il cer pentru 0.0.0.0 si 127.0.0.1; getaddrinfo nu trimite nimic pentru un literal), pe care paza il permite
|
|
const straine = r.jurnal.toate.filter((x) => (x.ev === 'connect' || x.ev === 'udp') && x.host !== '127.0.0.1' && x.host !== 'aere-paza-lookup.invalid');
|
|
return [r.jurnal.worker.length === 1 && r.jurnal.child.length === 1 && dinWorker && dinCopil && incarcari >= 3 && udp && rezolvat && straine.length === 0 && r.jurnal.dns.every((x) => x.method === 'dns.lookup' && x.allowed === true), `conexiuni locale ${conexiuni.length} (worker ${dinWorker}, copil ${dinCopil}), incarcari ale pazei ${incarcari}, udp conectat ${udp}, adresa din lookup ${rezolvat}, straine ${straine.length}, dns ${r.jurnal.dns.length}${r.jurnal.dns.length ? ' (' + r.jurnal.dns.map((x) => x.method + ':' + x.host + '@' + x.pid + '/' + x.tid).join(', ') + ')' : ''}, cod ${r.cod}`];
|
|
});
|
|
// a. TEVI: paza judeca CALEA asa cum o deschide Windows (normalizata), nu sirul dat. O teava locala ascultata aici numara sosirile;
|
|
// formele care numesc un server (prin '..', prin prefixul NT \??\, prin GLOBALROOT\Device\Mup) ajung la ea prin SMB pe loopback daca
|
|
// sunt lasate sa treaca. In blocare trebuie sa ajunga numai forma locala (controlul pozitiv). Doua plantari inverse in copii ale PAZEI:
|
|
// judecata pe sirul brut (forma de dinainte) si citirea unui sir ca port (connect(sir) e o teava pentru Node), fiecare lasa sa ajunga
|
|
const pazaCu = (d, plantari) => { for (const [vechi, nou] of plantari) { const n = planteaza(path.join(d, 'paza-retea.mjs'), vechi, nou); if (n !== 1) return `tinta plantarii in paza (${vechi.slice(0, 40)}) apare de ${n} ori`; } return null; };
|
|
if (process.platform !== 'win32') cer('a. tevile Windows: calea judecata dupa normalizare (forme cu .., \\??\\, GLOBALROOT, connect(sir))', 'NEMASURAT', 'formele de cale Windows se masoara numai pe Windows');
|
|
else await incearca('a. TEVI: in blocare, din formele care numesc un server (\\\\.\\pipe\\..\\UNC\\.., //./pipe/../UNC/.., \\\\.\\pipe\\..\\GLOBALROOT\\Device\\Mup\\.., \\??\\UNC\\.., \\??\\GLOBALROOT\\Device\\Mup\\.., si un sir dat lui socket.connect) NICIUNA nu ajunge la teava locala ascultata si fiecare e numita refuzata in jurnal; forma locala ajunge (controlul pozitiv); PLANTARILE INVERSE (judecata pe sirul brut; sirul citit ca port, cu tinta "localhost") lasa sa ajunga si ies ROSII', async () => {
|
|
const B1 = String.fromCharCode(92); const nume = 'aere-paza-r6-' + process.pid; const j = (...p) => p.join(B1);
|
|
const forme = { local: j('', '', '.', 'pipe', nume), dotdotUNC: j('', '', '.', 'pipe', '..', 'UNC', '127.0.0.1', 'pipe', nume), slashDotdotUNC: '//./pipe/../UNC/127.0.0.1/pipe/' + nume, dotdotMup: j('', '', '.', 'pipe', '..', 'GLOBALROOT', 'Device', 'Mup', '127.0.0.1', 'pipe', nume), ntUNC: j('', '??', 'UNC', '127.0.0.1', 'pipe', nume), ntMup: j('', '??', 'GLOBALROOT', 'Device', 'Mup', '127.0.0.1', 'pipe', nume) };
|
|
let sosiri = 0; const srv = (await import('node:net')).createServer((s) => { sosiri++; s.destroy(); });
|
|
await new Promise((res, rej) => { srv.on('error', rej); srv.listen(forme.local, res); });
|
|
try {
|
|
const lista = Object.entries(forme).map(([k, p]) => ({ k, p, sir: false })).concat([{ k: 'sirDotdotUNC', p: forme.dotdotUNC, sir: true }]);
|
|
// fiecare asteptare isi sterge ceasul cand se incheie: un ceas lasat armat tine procesul viu inca 4 s dupa ultima forma
|
|
const planta = `try { const netP = await import('node:net'); for (const x of ${JSON.stringify(lista)}) { await new Promise((res) => { const s = x.sir ? new netP.Socket() : null; const c = x.sir ? s.connect(x.p) : netP.connect(x.p); let t = null; const fin = () => { clearTimeout(t); res(); }; t = setTimeout(() => { c.destroy(); fin(); }, 4000); c.on('error', fin); c.on('connect', () => { c.destroy(); fin(); }); }); } } catch {}`;
|
|
const ruleazaTevi = async (dTeava) => { const n = planteaza(path.join(dTeava, 'raport.mjs'), '/*PLANTA:furnizor-strain*/', planta); if (n !== 1) return null; const inainte = sosiri; const r = await ruleazaAsincron({ dir: dTeava, args: ['--mod', '1', '--adrese', LISTA_TOT], tinte: 'localhost', pazaFisier: path.join(dTeava, 'paza-retea.mjs') }); await sleep(300); return { r, sosiri: sosiri - inainte }; };
|
|
const judeca = (x) => { if (!x || !x.r.jurnal) return { ok: false, det: 'jurnal lipsa' }; const c = x.r.jurnal.conexiuni; const refuz = (p, nr = 1) => c.filter((e) => e.path === p && e.allowed === false).length === nr; const ok = c.some((e) => e.path === forme.local && e.allowed === true) && refuz(forme.dotdotUNC, 2) && ['slashDotdotUNC', 'dotdotMup', 'ntUNC', 'ntMup'].every((k) => refuz(forme[k])) && x.sosiri === 1; return { ok, det: `sosiri ${x.sosiri}, permise ${c.filter((e) => e.allowed).map((e) => e.path || e.host).map((p) => String(p).slice(-28)).join(' ; ') || '-'}` }; };
|
|
const fwd = await ruleazaTevi(copie('teava')); if (!fwd) return 'marcajul nu apare o data';
|
|
const jf = judeca(fwd); if (!jf.ok) return [false, 'paza: ' + jf.det];
|
|
const dA = copie('teava-bruta'); const eA = pazaCu(dA, [['const n = caleNormalizata(s);', 'const n = s.split(\'/\').join(BS); if (!n.startsWith(BS + BS)) return true;'], [' && nume.length > 0 && !nume.includes(BS);', ';']]); if (eA) return eA;
|
|
const pA = await ruleazaTevi(dA); const jA = judeca(pA);
|
|
const dB = copie('teava-port'); const eB = pazaCu(dB, [["typeof a0 === 'string' && caNumar(a0) === false ? { path: a0 } : ", '']]); if (eB) return eB;
|
|
const pB = await ruleazaTevi(dB); const jB = judeca(pB);
|
|
return [jf.ok && !jA.ok && pA.sosiri > 1 && !jB.ok && pB.sosiri > 1, `paza: ${jf.det} | sirul brut: ${jA.det} | sirul ca port: ${jB.det}`];
|
|
} finally { srv.close(); }
|
|
});
|
|
// a. DNS: in configuratia modului 2 (tinta un literal IP si un nume), orice interogare in afara lui `lookup` pleaca spre un server DNS,
|
|
// oricare ar fi gazda numita. Serverele procesului sunt mutate, INAINTE de paza (--require), pe un ascultator UDP local care numara
|
|
// datagramele: nimic nu iese din masina, nici daca paza ar lasa sa treaca. setServers e refuzat (decide unde pleaca restul), iar o functie
|
|
// de interogare pe care o lista fixa nu o numea (resolveTlsa) e judecata si ea. Plantari inverse: regula veche (literalul tintei permis
|
|
// pentru orice metoda, setServers neinvelit) si lista fixa de nume; fiecare lasa datagrame sa ajunga
|
|
await incearca('a. DNS cu tinta declarata (literal 127.0.0.1 si un nume): reverse / resolveTxt / resolveTlsa / resolve4 pe tinta, setServers (modul, promises, Resolver, promises.Resolver, prototipul de baza) -> TOATE refuzate si ZERO datagrame la serverul DNS local; lookup pe literal permis (controlul pozitiv); PLANTARILE INVERSE (regula veche a literalului + setServers neinvelit; lista fixa de nume) lasa datagrame si ies ROSII', async () => {
|
|
// ascultatorul raspunde fiecarei interogari cu REFUSED (antetul intors, QR=1, RCODE=5): resolverul nu mai reincearca si procesul
|
|
// iese imediat, in loc sa astepte zeci de secunde dupa un server care tace
|
|
const dg = await import('node:dgram'); let datagrame = 0; const sv = dg.createSocket('udp4');
|
|
sv.on('message', (msg, ri) => { datagrame++; if (msg.length >= 12) { const r = Buffer.from(msg); r[2] |= 0x80; r[3] = (r[3] & 0xf0) | 0x05; sv.send(r, ri.port, ri.address, () => {}); } });
|
|
const port = await new Promise((res, rej) => { sv.on('error', rej); sv.bind(0, '127.0.0.2', () => res(sv.address().port)); });
|
|
try {
|
|
const lo = '127.0.0.2:' + port; const pre = path.join(T, 'dns-local.cjs');
|
|
fs.writeFileSync(pre, `const d = require('node:dns'); d.setServers([${JSON.stringify(lo)}]); d.promises.setServers([${JSON.stringify(lo)}]);\n`);
|
|
const cap = 'const cap = (p) => { let t = null; return Promise.race([p, new Promise((r) => { t = setTimeout(r, 1500); })]).catch(() => {}).finally(() => clearTimeout(t)); };';
|
|
const planta = [
|
|
`try { const dn = await import('node:dns'); ${cap}`,
|
|
"await cap(new Promise((r) => dn.reverse('127.0.0.1', () => r())));",
|
|
"await cap(dn.promises.resolveTxt('127.0.0.1'));",
|
|
"if (typeof dn.resolveTlsa === 'function') await cap(new Promise((r) => dn.resolveTlsa('127.0.0.1', () => r())));",
|
|
"await cap(new Promise((r) => dn.resolve4('provider.example.test', () => r())));",
|
|
`try { dn.setServers([${JSON.stringify(lo)}]); } catch {}`,
|
|
`try { dn.promises.setServers([${JSON.stringify(lo)}]); } catch {}`,
|
|
`for (const R of [dn.Resolver, dn.promises.Resolver]) { const r = new R({ timeout: 300, tries: 1 }); let ok = true; try { r.setServers([${JSON.stringify(lo)}]); } catch { ok = false; } if (ok) await cap(R === dn.Resolver ? new Promise((x) => r.resolve4('127.0.0.1', () => x())) : r.resolve4('127.0.0.1')); }`,
|
|
`try { Object.getPrototypeOf(dn.Resolver.prototype).setServers.call(new dn.Resolver(), [${JSON.stringify(lo)}]); } catch {}`,
|
|
"await cap(dn.promises.lookup('127.0.0.1'));",
|
|
'} catch {}',
|
|
].join('\n ');
|
|
const ruleazaDns = async (d) => { const n = planteaza(path.join(d, 'raport.mjs'), '/*PLANTA:furnizor-strain*/', planta); if (n !== 1) return null; const inainte = datagrame; const r = await ruleazaAsincron({ dir: d, args: ['--mod', '1', '--adrese', LISTA_TOT], tinte: '127.0.0.1:9,provider.example.test:443', pazaFisier: path.join(d, 'paza-retea.mjs'), preincarcate: [pre] }); await sleep(400); return { r, datagrame: datagrame - inainte }; };
|
|
const areTlsa = typeof (await import('node:dns')).resolveTlsa === 'function';
|
|
const judeca = (x) => { if (!x || !x.r.jurnal) return { ok: false, det: 'jurnal lipsa' }; const dn = x.r.jurnal.dns; const refuz = (m, h) => dn.some((e) => e.method === m && (!h || e.host === h) && e.allowed === false); const servere = dn.filter((e) => /setServers$/.test(e.method) && e.allowed === false).length; const ok = refuz('dns.reverse', '127.0.0.1') && refuz('dns.promises.resolveTxt', '127.0.0.1') && (!areTlsa || refuz('dns.resolveTlsa', '127.0.0.1')) && refuz('dns.resolve4', 'provider.example.test') && servere >= 5 && dn.some((e) => e.method === 'dns.promises.lookup' && e.host === '127.0.0.1' && e.allowed === true) && x.datagrame === 0; return { ok, det: `datagrame ${x.datagrame}, setServers refuzate ${servere}, permise ${dn.filter((e) => e.allowed).map((e) => e.method + ':' + e.host).join(' ; ') || '-'}` }; };
|
|
const fwd = await ruleazaDns(copie('dns')); if (!fwd) return 'marcajul nu apare o data';
|
|
const jf = judeca(fwd); if (!jf.ok) return [false, 'paza: ' + jf.det];
|
|
const dA = copie('dns-vechi'); const eA = pazaCu(dA, [["const permisNume = (host, metoda) => TOT || (metoda === 'lookup' && (net.isIP(String(host)) !== 0 || TINTE.some((t) => t === String(host) || t.startsWith(String(host) + ':'))));", "const permisNume = (host, metoda) => TOT || TINTE.some((t) => t === String(host) || t.startsWith(String(host) + ':')) || (metoda === 'lookup' && net.isIP(String(host)) !== 0);"], ['function invelesteServere(obiect, eticheta) {', 'function invelesteServere(obiect, eticheta) { return;']]); if (eA) return eA;
|
|
const pA = await ruleazaDns(dA); const jA = judeca(pA);
|
|
const dB = copie('dns-lista'); const eB = pazaCu(dB, [['for (const nume of Object.getOwnPropertyNames(obiect)) {', "for (const nume of ['lookup', 'lookupService', 'resolve', 'resolve4', 'resolve6', 'resolveAny', 'resolveCaa', 'resolveCname', 'resolveMx', 'resolveNaptr', 'resolveNs', 'resolvePtr', 'resolveSoa', 'resolveSrv', 'resolveTxt', 'reverse'].filter((x) => typeof obiect[x] === 'function')) {"]]); if (eB) return eB;
|
|
const pB = await ruleazaDns(dB); const jB = judeca(pB);
|
|
return [jf.ok && !jA.ok && pA.datagrame > 0 && !jB.ok && (!areTlsa || pB.datagrame > 0), `paza: ${jf.det} | regula veche: ${jA.det.slice(0, 60)} | lista fixa: ${jB.det.slice(0, 60)}${areTlsa ? '' : ' (resolveTlsa lipseste in acest Node: lista fixa nu se poate deosebi)'}`];
|
|
} finally { sv.close(); }
|
|
});
|
|
// a. UDP: unde pleaca o datagrama se citeste ca in Node (un socket CONECTAT trimite la adresa lui, offset/lungime nu sunt port; portul poate
|
|
// fi un sir; fara adresa = loopback), iar o aderare multicast (care face sistemul sa trimita un raport IGMP) e jurnalizata si refuzata.
|
|
// Ascultatorul local e tinta declarata: cele trei trimiteri spre el ajung, una spre alt port e refuzata
|
|
await incearca('a. UDP spre tinta declarata: send(buf, offset, lungime) pe un socket CONECTAT, send(buf, 0, 4, port, adresa), send(buf, "port", adresa), sendto(buf, 0, 4, port, adresa) -> PERMISE cu portul tintei, 4 datagrame primite; send spre alt port refuzat; addMembership (pe interfata loopback) refuzat; PLANTARILE INVERSE (citirea veche a argumentelor; aderarea neinvelita) ies ROSII', async () => {
|
|
const dg = await import('node:dgram'); let primite = 0; const sv = dg.createSocket('udp4'); sv.on('message', () => { primite++; });
|
|
const port = await new Promise((res, rej) => { sv.on('error', rej); sv.bind(0, '127.0.0.1', () => res(sv.address().port)); });
|
|
try {
|
|
const semn = path.join(T, 'semn-membership.txt');
|
|
const trimite = '(s, ...a) => new Promise((r) => { let t = null; const fin = () => { clearTimeout(t); r(); }; t = setTimeout(fin, 1500); try { s.send(...a, fin); } catch { fin(); } })';
|
|
const planta = [
|
|
`try { const dgP = await import('node:dgram'); const fsP = await import('node:fs'); const trimite = ${trimite};`,
|
|
`const s = dgP.createSocket('udp4'); s.on('error', () => {}); await new Promise((r) => s.connect(${port}, '127.0.0.1', r)); await trimite(s, Buffer.from('aere-a'), 1, 2);`,
|
|
`const u = dgP.createSocket('udp4'); u.on('error', () => {}); await trimite(u, Buffer.from('aere-b'), 0, 4, ${port}, '127.0.0.1'); await trimite(u, Buffer.from('aere-c'), ${JSON.stringify(String(port))}, '127.0.0.1'); await trimite(u, Buffer.from('aere-d'), 9, '127.0.0.1');`,
|
|
// sendto (forma veche a API-ului, cu toti parametrii obligatorii) trece prin send: si ea e judecata
|
|
`await new Promise((r) => { let t = null; const fin = () => { clearTimeout(t); r(); }; t = setTimeout(fin, 1500); try { u.sendto(Buffer.from('aere-e'), 0, 4, ${port}, '127.0.0.1', fin); } catch { fin(); } });`,
|
|
`const mc = dgP.createSocket({ type: 'udp4', reuseAddr: true }); mc.on('error', () => {}); await new Promise((r) => mc.bind(0, '127.0.0.1', r)); try { mc.addMembership('239.255.0.77', '127.0.0.1'); fsP.appendFileSync(${JSON.stringify(semn)}, 'membership;'); mc.dropMembership('239.255.0.77', '127.0.0.1'); } catch {}`,
|
|
's.close(); u.close(); mc.close(); } catch {}',
|
|
].join('\n ');
|
|
const ruleazaUdp = async (d) => { const n = planteaza(path.join(d, 'raport.mjs'), '/*PLANTA:furnizor-strain*/', planta); if (n !== 1) return null; try { fs.rmSync(semn, { force: true }); } catch {} const inainte = primite; const r = await ruleazaAsincron({ dir: d, args: ['--mod', '1', '--adrese', LISTA_TOT], tinte: '127.0.0.1:' + port, pazaFisier: path.join(d, 'paza-retea.mjs') }); await sleep(300); return { r, primite: primite - inainte, aderat: fs.existsSync(semn) }; };
|
|
const judeca = (x) => { if (!x || !x.r.jurnal) return { ok: false, det: 'jurnal lipsa' }; const u = x.r.jurnal.udp; const spreTinta = u.filter((e) => e.host === '127.0.0.1' && e.port === port && e.allowed === true).length; const ok = spreTinta >= 4 && u.some((e) => e.host === '127.0.0.1' && e.port === 9 && e.allowed === false) && u.some((e) => e.method === 'addMembership' && e.allowed === false) && !x.aderat && x.primite === 4 && u.every((e) => e.method || e.port === port || e.port === 9); return { ok, det: `primite ${x.primite}, permise spre tinta ${spreTinta}, aderat ${x.aderat}, porturi ${[...new Set(u.filter((e) => !e.method).map((e) => e.port))].join(',')}` }; };
|
|
const fwd = await ruleazaUdp(copie('udp')); if (!fwd) return 'marcajul nu apare o data';
|
|
const jf = judeca(fwd); if (!jf.ok) return [false, 'paza: ' + jf.det];
|
|
const dA = copie('udp-vechi'); const eA = pazaCu(dA, [['const { host, port } = tintaUdp(this, args);', "let port = args.find((x, i) => i > 0 && typeof x === 'number'); let host = args.find((x, i) => i > 1 && typeof x === 'string') || null; if (!host) { try { const r = this.remoteAddress(); host = r.address; port = port || r.port; } catch { } }"]]); if (eA) return eA;
|
|
const pA = await ruleazaUdp(dA); const jA = judeca(pA);
|
|
const dB = copie('udp-aderare'); const eB = pazaCu(dB, [["for (const nume of ['addMembership', 'dropMembership', 'addSourceSpecificMembership', 'dropSourceSpecificMembership']) {", 'for (const nume of []) {']]); if (eB) return eB;
|
|
const pB = await ruleazaUdp(dB); const jB = judeca(pB);
|
|
return [jf.ok && !jA.ok && pA.primite < 4 && !jB.ok && pB.aderat, `paza: ${jf.det} | citirea veche: ${jA.det} | aderarea neinvelita: ${jB.det}`];
|
|
} finally { sv.close(); }
|
|
});
|
|
// b. raspuns gol / eroare, pe toate adaptoarele, cu plantarea inversa
|
|
for (const [fel, fixtura, flag] of [['electrum', 'electrum.json', '--electrum'], ['esplora', 'esplora.json', '--esplora'], ['bitcoind', 'bitcoind.json', '--bitcoind'], ['eth', 'eth-rpc.json', '--eth-rpc']]) {
|
|
const m = await porneste(fel, path.join(FIX, fixtura), ['--gol']);
|
|
const r = ruleaza({ args: ['--mod', '2', '--adrese', fel === 'eth' ? LISTA_ETH : LISTA_BTC, flag, fel === 'electrum' ? m.tinta : m.url], tinte: m.tinta });
|
|
m.opreste();
|
|
cer(`b. ${fel} --gol: fiecare adresa iese NEMASURAT numit (cod 2), niciuna "no on-chain evidence"`, da(predicatGol(r)), r.raport ? detaliu(r.raport, r.raport.addresses[0].input).slice(0, 100) : r.err.slice(0, 100));
|
|
}
|
|
await incearca('b. PLANTAREA INVERSA: o copie in care un raspuns gol al tintei Electrum devine "istoric gol" face verificarea de mai sus ROSIE (nu o crapa)', async () => {
|
|
const d = copie('gol');
|
|
const n = planteaza(path.join(d, 'adaptoare.mjs'), "} catch (e) { return { error: 'Electrum target ' + tinta.host + ':' + tinta.port + ': ' + taie(e.message), requests: cereri }; }", '} catch (e) { return observatieDinTranzactii(t, [], { requests: cereri }); }');
|
|
// un raspuns gol nu aduce nici varful: plasa "fara varf citit, niciodata F" (runda 6) se scoate si ea, ca defectul vechi sa se vada
|
|
const n2 = planteaza(path.join(d, 'clasifica.mjs'), "} else if (r.label === ETICHETA.FARA_DOVADA) r = nem(r, 'not measured: the tip of the target was not read", "} else if (false) r = nem(r, 'not measured: the tip of the target was not read");
|
|
if (n !== 1 || n2 !== 1) return `tinta plantarii apare de ${n} / ${n2} ori`;
|
|
const m = await porneste('electrum', path.join(FIX, 'electrum.json'), ['--gol']);
|
|
const r = ruleaza({ dir: d, args: ['--mod', '2', '--adrese', LISTA_BTC, '--electrum', m.tinta], tinte: m.tinta }); m.opreste();
|
|
return [predicatGol(r) === false && !!r.raport && r.raport.summary.noOnChainEvidence > 0, `copia plantata a dat ${r.raport ? r.raport.summary.verdict : 'niciun raport'}`];
|
|
});
|
|
// c. tinta muta
|
|
{
|
|
const m = await porneste('electrum', path.join(FIX, 'electrum.json'), ['--mut']);
|
|
const r = ruleaza({ args: ['--mod', '2', '--adrese', scrieLista('unu', [A.p2pkh_necheltuit]), '--electrum', m.tinta], tinte: m.tinta, env: { AERE_EXPUNERE_TIMEOUT_MS: '1500' } }); m.opreste();
|
|
cer('c. tinta muta (conexiune inchisa fara raspuns): NEMASURAT numit, nu "no on-chain evidence"', da(r.cod === 2 && nemasuratCu(r.raport, A.p2pkh_necheltuit, 'not measured:')), r.raport ? detaliu(r.raport, A.p2pkh_necheltuit).slice(0, 100) : '');
|
|
}
|
|
// d. cheltuiri plantate pe adrese sintetice
|
|
await incearca('d. adresa sintetica P2PKH: numai finantarea -> F (controlul pozitiv al metodei); cu o cheltuire PLANTATA (cheia in scriptSig) -> E', async () => {
|
|
const k = cheie('p2pkh'); const h = B.hash160(k.pub); const spk = p2pkhSpk(h); const adresa = b58check(0, h);
|
|
const f = finantare(spk, 'p2pkh'); const s = cheltuire(f.id, Buffer.concat([push(SIG), push(k.pub)]), []);
|
|
const lista = scrieLista('sintetic-p2pkh', [adresa]);
|
|
const r1 = await electrumCu(suprascriereElectrum('planta-fara', spk, [{ ...f, height: 1 }]), lista);
|
|
const r2 = await electrumCu(suprascriereElectrum('planta-cu', spk, [{ ...f, height: 1 }, { ...s, height: 2 }]), lista);
|
|
return [r1.cod === 0 && eticheta(r1.raport, adresa) === F_ && r2.cod === 0 && eticheta(r2.raport, adresa) === E_ && dovada(r2.raport, adresa, 'public key revealed in the scriptSig'), `${detaliu(r1.raport, adresa)} -> ${detaliu(r2.raport, adresa)}`];
|
|
});
|
|
await incearca('d. P2SH cu redeem script in forma P2PKH (scriptSig = <sig> <cheie> <redeem>): cheia e un push separat si e gasita -> E', async () => {
|
|
const k = cheie('p2sh-p2pkh'); const redeem = p2pkhSpk(B.hash160(k.pub)); const hr = B.hash160(redeem);
|
|
const spk = Buffer.concat([Buffer.from([0xa9, 0x14]), hr, Buffer.from([0x87])]); const adresa = b58check(5, hr);
|
|
const f = finantare(spk, 'p2sh-p2pkh'); const s = cheltuire(f.id, Buffer.concat([push(SIG), push(k.pub), push(redeem)]), []);
|
|
const r = await electrumCu(suprascriereElectrum('planta-p2sh', spk, [{ ...f, height: 1 }, { ...s, height: 2 }]), scrieLista('sintetic-p2sh', [adresa]));
|
|
return [r.cod === 0 && eticheta(r.raport, adresa) === E_ && dovada(r.raport, adresa, 'commits to HASH160 of a key'), detaliu(r.raport, adresa)];
|
|
});
|
|
await incearca('d. P2WSH cu witness script in forma P2PKH (witness = [sig, cheie, script]): cheia e gasita -> E', async () => {
|
|
const k = cheie('p2wsh-p2pkh'); const ws = p2pkhSpk(B.hash160(k.pub)); const prog = B.sha256(ws);
|
|
const spk = Buffer.concat([Buffer.from([0x00, 0x20]), prog]); const intrare = 'script:' + B.hex(spk);
|
|
const f = finantare(spk, 'p2wsh-p2pkh'); const s = cheltuire(f.id, Buffer.alloc(0), [SIG, k.pub, ws]);
|
|
const r = await electrumCu(suprascriereElectrum('planta-p2wsh', spk, [{ ...f, height: 1 }, { ...s, height: 2 }]), scrieLista('sintetic-p2wsh', [intrare]));
|
|
return [r.cod === 0 && eticheta(r.raport, intrare) === E_ && dovada(r.raport, intrare, 'commits to HASH160 of a key'), detaliu(r.raport, intrare)];
|
|
});
|
|
await incearca('d. cheltuirea e doar in MEMPOOL (Electrum height 0; Esplora status.confirmed false): NEMASURAT numit ("public there but not yet on chain"), nu E si nu F, pe amandoua adaptoarele', async () => {
|
|
const k = cheie('mempool'); const h = B.hash160(k.pub); const spk = p2pkhSpk(h); const adresa = b58check(0, h);
|
|
const f = finantare(spk, 'mempool'); const s = cheltuire(f.id, Buffer.concat([push(SIG), push(k.pub)]), []);
|
|
const lista = scrieLista('sintetic-mempool', [adresa]); const istoric = [{ ...f, height: 1 }, { ...s, height: 0 }];
|
|
const r1 = await electrumCu(suprascriereElectrum('planta-mempool', spk, istoric), lista);
|
|
const r2 = await esploraCu(suprascriereEsplora('planta-mempool-es', adresa, istoric), lista);
|
|
// controlul pozitiv al machetei Esplora: aceeasi cheltuire CONFIRMATA (height 2) -> E
|
|
const r3 = await esploraCu(suprascriereEsplora('planta-mempool-es-conf', adresa, [{ ...f, height: 1 }, { ...s, height: 2 }]), lista);
|
|
return [r1.cod === 2 && nemasuratCu(r1.raport, adresa, 'public there but not yet on chain') && r2.cod === 2 && nemasuratCu(r2.raport, adresa, 'public there but not yet on chain') && r3.cod === 0 && eticheta(r3.raport, adresa) === E_, `electrum: ${detaliu(r1.raport, adresa).slice(0, 70)} | esplora: ${detaliu(r2.raport, adresa).slice(0, 70)} | esplora confirmata: ${detaliu(r3.raport, adresa)}`];
|
|
});
|
|
await incearca('d. RBF: cheltuirea difuzata a fost INLOCUITA (istoricul mai are doar finantarea) -> F, dar "a spend broadcast and never confirmed" e numit in "not seen" si in intrebarea de declaratie; Esplora cu mempool_stats care numara o cheltuire pe care pagina nu o poarta -> NEMASURAT', async () => {
|
|
const k = cheie('rbf'); const h = B.hash160(k.pub); const spk = p2pkhSpk(h); const adresa = b58check(0, h);
|
|
const f = finantare(spk, 'rbf'); const lista = scrieLista('sintetic-rbf', [adresa]);
|
|
const r1 = await electrumCu(suprascriereElectrum('planta-rbf', spk, [{ ...f, height: 1 }]), lista);
|
|
const stats = { address: adresa, chain_stats: { funded_txo_count: 1, funded_txo_sum: 100000, spent_txo_count: 0, spent_txo_sum: 0, tx_count: 1 }, mempool_stats: { funded_txo_count: 0, funded_txo_sum: 0, spent_txo_count: 1, spent_txo_sum: 100000, tx_count: 1 } };
|
|
const fx = path.join(T, 'planta-rbf-es.json'); fs.writeFileSync(fx, JSON.stringify({ raspunsuri: { ['/address/' + adresa]: stats, ['/address/' + adresa + '/txs']: [esploraTx({ ...f, height: 1 })] } }));
|
|
const r2 = await esploraCu(fx, lista);
|
|
return [r1.cod === 0 && eticheta(r1.raport, adresa) === F_ && nevazut(r1.raport, adresa, /broadcast and never confirmed/) && /broadcast and never confirmed/.test(r1.md) && r2.cod === 2 && nemasuratCu(r2.raport, adresa, 'mempool_stats'), `electrum dupa RBF: ${detaliu(r1.raport, adresa)} | esplora mempool_stats: ${detaliu(r2.raport, adresa).slice(0, 90)}`];
|
|
});
|
|
await incearca('d. P2TR cu iesirea CONFIRMATA si o cheltuire script-path doar in MEMPOOL ramane E (cheltuirea din mempool nu scade dovada de pe lant), cu tranzactia neconfirmata numita in "not seen"', async () => {
|
|
const k = cheie('p2tr-mempool'); const spk = Buffer.concat([Buffer.from([0x51, 0x20]), k.pub.subarray(1)]); const intrare = 'script:' + B.hex(spk);
|
|
const f = finantare(spk, 'p2tr-mempool'); const cb = Buffer.concat([Buffer.from([0xc0]), Buffer.alloc(32, 2)]);
|
|
const s = cheltuire(f.id, Buffer.alloc(0), [Buffer.alloc(64, 1), Buffer.from([0x51]), cb]);
|
|
const r = await electrumCu(suprascriereElectrum('planta-p2tr-mempool', spk, [{ ...f, height: 1 }, { ...s, height: 0 }]), scrieLista('sintetic-p2tr-mempool', [intrare]));
|
|
return [r.cod === 0 && eticheta(r.raport, intrare) === E_ && nevazut(r.raport, intrare, /1 unconfirmed transaction/), detaliu(r.raport, intrare) + ' | ' + (gaseste(r.raport, intrare) || { notSeen: [] }).notSeen.filter((n) => /unconfirmed/.test(n)).join('; ').slice(0, 90)];
|
|
});
|
|
await incearca('d. PLANTAREA INVERSA: o copie cu conditia veche (cheltuirea din mempool da NEMASURAT si pe P2TR cu iesire confirmata) face verificarea de mai sus ROSIE', async () => {
|
|
const d = copie('p2tr'); const n = planteaza(path.join(d, 'clasifica.mjs'), 'if (!revel.length && obs.unconfirmedRevealing && !expusPrinTip)', 'if (!revel.length && obs.unconfirmedRevealing)');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const k = cheie('p2tr-mempool'); const spk = Buffer.concat([Buffer.from([0x51, 0x20]), k.pub.subarray(1)]); const intrare = 'script:' + B.hex(spk);
|
|
const f = finantare(spk, 'p2tr-mempool'); const cb = Buffer.concat([Buffer.from([0xc0]), Buffer.alloc(32, 2)]);
|
|
const s = cheltuire(f.id, Buffer.alloc(0), [Buffer.alloc(64, 1), Buffer.from([0x51]), cb]);
|
|
const m = await porneste('electrum', path.join(FIX, 'electrum.json'), ['--suprascrie', suprascriereElectrum('planta-p2tr-mempool-inv', spk, [{ ...f, height: 1 }, { ...s, height: 0 }])]);
|
|
const r = ruleaza({ dir: d, args: ['--mod', '2', '--adrese', scrieLista('sintetic-p2tr-mempool-inv', [intrare]), '--electrum', m.tinta], tinte: m.tinta }); m.opreste();
|
|
return [!(r.cod === 0 && eticheta(r.raport, intrare) === E_), `copia plantata a dat ${detaliu(r.raport, intrare).slice(0, 80)} (cod ${r.cod})`];
|
|
});
|
|
await incearca('d. witness script P2WSH fara forma standard: un push de 33 de octeti care NU e operandul unei verificari de semnatura (chiar daca e punct valid) -> NEMASURAT ("not verified as keys bound"); <cheie> OP_CHECKSIG intr-un script nestandard -> E', async () => {
|
|
const G = Buffer.from('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', 'hex');
|
|
const fals = Buffer.concat([Buffer.from([2]), crypto.createHash('sha256').update('nu e cheie').digest()]);
|
|
const cazuri = [
|
|
['fals-drop', Buffer.concat([push(fals), Buffer.from([0x75, 0x51])]), U_, 'not verified as keys bound'], // <33 B> OP_DROP OP_1
|
|
['cheie-drop', Buffer.concat([push(G), Buffer.from([0x75, 0x51])]), U_, 'not verified as keys bound'], // <G> OP_DROP OP_1
|
|
['cheie-checksig', Buffer.concat([Buffer.from([0x51, 0x75]), push(G), Buffer.from([0xac])]), E_, 'operand of a signature check'], // OP_1 OP_DROP <G> OP_CHECKSIG
|
|
];
|
|
const rez = [];
|
|
for (const [nume, ws, asteptat, fragment] of cazuri) {
|
|
const spk = Buffer.concat([Buffer.from([0x00, 0x20]), B.sha256(ws)]); const intrare = 'script:' + B.hex(spk);
|
|
const f = finantare(spk, nume); const s = cheltuire(f.id, Buffer.alloc(0), [SIG, ws]);
|
|
const r = await electrumCu(suprascriereElectrum('planta-ws-' + nume, spk, [{ ...f, height: 1 }, { ...s, height: 2 }]), scrieLista('sintetic-ws-' + nume, [intrare]));
|
|
const a = gaseste(r.raport, intrare); const text = a ? String(a.unmeasured || '') + ' ' + (a.evidence || []).join(' ') : '';
|
|
rez.push([eticheta(r.raport, intrare) === asteptat && text.includes(fragment), `${nume}=${eticheta(r.raport, intrare)}`]);
|
|
}
|
|
return [rez.every((x) => x[0]), rez.map((x) => x[1]).join(' ')];
|
|
});
|
|
await incearca('d. Esplora, istoric sintetic de 31 de tranzactii pe DOUA pagini (/txs apoi /txs/chain/<ultimul>): cheltuirea cu cheia e pe pagina a doua -> E; cu pagina a doua lipsa -> NEMASURAT ("served 25 ... of the 31")', async () => {
|
|
const k = cheie('paginare'); const h = B.hash160(k.pub); const spk = p2pkhSpk(h); const adresa = b58check(0, h);
|
|
const fonduri = Array.from({ length: 30 }, (_, i) => ({ ...finantare(spk, 'paginare ' + i), height: 100 + i }));
|
|
const s = { ...cheltuire(fonduri[0].id, Buffer.concat([push(SIG), push(k.pub)]), []), height: 101 }; // veche: cade pe pagina a doua
|
|
const toate = [...fonduri, s].sort((x, y) => y.height - x.height); // Esplora serveste de la cea mai noua
|
|
const pagini = toate.map(esploraTx); const p1 = pagini.slice(0, 25), p2 = pagini.slice(25);
|
|
const stats = { address: adresa, chain_stats: { funded_txo_count: 30, funded_txo_sum: 3000000, spent_txo_count: 1, spent_txo_sum: 100000, tx_count: 31 }, mempool_stats: { funded_txo_count: 0, funded_txo_sum: 0, spent_txo_count: 0, spent_txo_sum: 0, tx_count: 0 } };
|
|
const cu = path.join(T, 'planta-pag-cu.json'); fs.writeFileSync(cu, JSON.stringify({ raspunsuri: { ['/address/' + adresa]: stats, ['/address/' + adresa + '/txs']: p1, ['/address/' + adresa + '/txs/chain/' + p1[24].txid]: p2 } }));
|
|
const fara = path.join(T, 'planta-pag-fara.json'); fs.writeFileSync(fara, JSON.stringify({ raspunsuri: { ['/address/' + adresa]: stats, ['/address/' + adresa + '/txs']: p1 } }));
|
|
const lista = scrieLista('sintetic-pag', [adresa]);
|
|
const mA = await porneste('esplora', path.join(FIX, 'esplora.json'), ['--suprascrie', cu]); const r1 = ruleaza({ args: ['--mod', '2', '--adrese', lista, '--esplora', mA.url], tinte: mA.tinta }); await sleep(100); const c1 = mA.contor(); mA.opreste();
|
|
const mB = await porneste('esplora', path.join(FIX, 'esplora.json'), ['--suprascrie', fara]); const r2 = ruleaza({ args: ['--mod', '2', '--adrese', lista, '--esplora', mB.url], tinte: mB.tinta }); mB.opreste();
|
|
return [r1.cod === 0 && eticheta(r1.raport, adresa) === E_ && dovada(r1.raport, adresa, 'public key revealed in the scriptSig') && c1.requests >= 3 && r2.cod === 2 && nemasuratCu(r2.raport, adresa, 'served 25 confirmed transactions of the 31'), `${detaliu(r1.raport, adresa)} (${c1.requests} cereri) -> ${detaliu(r2.raport, adresa)}`];
|
|
});
|
|
// d. o cheltuire VAZUTA a unei adrese de tip hash dezvaluie, prin consens, preimaginea hash-ului: niciodata F. Cheia hibrida (06/07, valida
|
|
// in consens) si cheia urmata de un OP_NOP se citesc (E); o cheltuire in care nu se gaseste nicio preimagine iese NEMASURAT numit
|
|
const cazuriChei = (() => {
|
|
const k = cheie('chei-necitite'); const u = Buffer.from(secp256k1.getPublicKey(k.priv, false));
|
|
const hibrid = Buffer.concat([Buffer.from([(u[64] & 1) ? 7 : 6]), u.subarray(1)]); const hibridRau = Buffer.concat([Buffer.from([hibrid[0] ^ 1]), u.subarray(1)]);
|
|
const p2wpkh = (h) => Buffer.concat([Buffer.from([0x00, 0x14]), h]);
|
|
return [
|
|
// [nume, intrarea din lista, scriptPubKey, scriptSig, witness, eticheta asteptata, fragment]
|
|
['P2PKH, cheie HIBRIDA in scriptSig', b58check(0, B.hash160(hibrid)), p2pkhSpk(B.hash160(hibrid)), Buffer.concat([push(SIG), push(hibrid)]), [], E_, 'public key revealed in the scriptSig'],
|
|
['P2PKH, <sig> <cheie necomprimata> OP_NOP', b58check(0, B.hash160(u)), p2pkhSpk(B.hash160(u)), Buffer.concat([push(SIG), push(u), Buffer.from([0x61])]), [], E_, 'public key revealed in the scriptSig'],
|
|
['P2WPKH, cheie HIBRIDA in witness', 'script:' + B.hex(p2wpkh(B.hash160(hibrid))), p2wpkh(B.hash160(hibrid)), Buffer.alloc(0), [SIG, hibrid], E_, 'public key revealed in the witness'],
|
|
['P2PKH, hibrida cu PARITATEA gresita (nu e cheie: libsecp256k1 o refuza)', b58check(0, B.hash160(hibridRau)), p2pkhSpk(B.hash160(hibridRau)), Buffer.concat([push(SIG), push(hibridRau)]), [], U_, 'not decoded as a public key'],
|
|
['P2PKH, cheltuire fara nicio preimagine (<sig> OP_NOP)', b58check(0, B.hash160(k.pub)), p2pkhSpk(B.hash160(k.pub)), Buffer.concat([push(SIG), Buffer.from([0x61])]), [], U_, 'in a form this tool does not read'],
|
|
];
|
|
})();
|
|
const ruleazaCazChei = async (dir, [nume, intrare, spk, ss, w]) => { const f = finantare(spk, 'chei ' + nume); const s = cheltuire(f.id, ss, w); const m = await porneste('electrum', path.join(FIX, 'electrum.json'), ['--suprascrie', suprascriereElectrum('planta-chei-' + nume.replace(/\W+/g, '-'), spk, [{ ...f, height: 1 }, { ...s, height: 2 }])]); const r = ruleaza({ dir, args: ['--mod', '2', '--adrese', scrieLista('chei-' + nume.replace(/\W+/g, '-'), [intrare]), '--electrum', m.tinta], tinte: m.tinta }); m.opreste(); return r; };
|
|
const judecaCazChei = (r, [nume, intrare, , , , asteptat, fragment]) => { const a = gaseste(r.raport, intrare); const text = a ? String(a.unmeasured || '') + ' ' + (a.evidence || []).join(' ') : ''; return eticheta(r.raport, intrare) === asteptat && text.includes(fragment); };
|
|
await incearca('d. cheltuiri VAZUTE ale adreselor de tip hash, cu cheia in forme rare: cheia hibrida 06/07 (P2PKH si P2WPKH) si <sig> <cheie> OP_NOP -> E; o hibrida cu paritatea gresita si o cheltuire fara nicio preimagine -> NEMASURAT numit; niciuna F', async () => {
|
|
const rez = []; for (const c of cazuriChei) { const r = await ruleazaCazChei(AICI, c); rez.push([judecaCazChei(r, c) && eticheta(r.raport, c[1]) !== F_, `${c[0]}=${eticheta(r.raport, c[1])}`]); }
|
|
return [rez.every((x) => x[0]), rez.map((x) => x[1]).join(' | ')];
|
|
});
|
|
await incearca('d. PLANTAREA INVERSA: o copie fara regula "cheltuire vazuta fara preimagine citita -> NEMASURAT" da F pe cheltuirea fara preimagine, si verificarea de mai sus iese ROSIE', async () => {
|
|
const d = copie('chei'); const n = planteaza(path.join(d, 'clasifica.mjs'), 'if (!revel.length && !tipExpus && spendsVazute.length) return nem(', 'if (false) return nem(');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const c = cazuriChei[cazuriChei.length - 1]; const r = await ruleazaCazChei(d, c);
|
|
return [!judecaCazChei(r, c) && eticheta(r.raport, c[1]) === F_, `copia plantata a dat ${detaliu(r.raport, c[1]).slice(0, 80)} (cod ${r.cod})`];
|
|
});
|
|
// d. o cheltuire VAZUTA prin egalitate de hash, fara finantarea ei: adresa P2SH-P2WPKH, cheltuirea servita FARA witness (id-ul nu
|
|
// acopera witness-ul), istoricul serverului fara tranzactia de finantare. Redeem-ul din scriptSig se hashuieste la adresa: preimaginea
|
|
// e pe lant, deci nu F; cu witness-ul (cheia) aceeasi cheltuire e E
|
|
const k12 = (() => {
|
|
const k = cheie('k12'); const redeem = Buffer.concat([Buffer.from([0x00, 0x14]), B.hash160(k.pub)]); const hr = B.hash160(redeem);
|
|
const spk = Buffer.concat([Buffer.from([0xa9, 0x14]), hr, Buffer.from([0x87])]); const adresa = b58check(5, hr);
|
|
const idF = crypto.createHash('sha256').update('k12 finantare nedata').digest('hex');
|
|
return { adresa, spk, fara: cheltuire(idF, push(redeem), []), cu: cheltuire(idF, push(redeem), [SIG, k.pub]) };
|
|
})();
|
|
const ruleazaK12 = async (dir, x, nume) => { const m = await porneste('electrum', path.join(FIX, 'electrum.json'), ['--suprascrie', suprascriereElectrum('planta-k12-' + nume, k12.spk, [{ ...x, height: 2 }])]); const r = ruleaza({ dir, args: ['--mod', '2', '--adrese', scrieLista('k12-' + nume, [k12.adresa]), '--electrum', m.tinta], tinte: m.tinta }); m.opreste(); return r; };
|
|
await incearca('d. o cheltuire VAZUTA prin egalitate de hash (P2SH-P2WPKH: redeem-ul se hashuieste la adresa), servita FARA witness si FARA finantare -> NEMASURAT ("spend(s) of this address seen"), nu F; cu witness-ul -> E (controlul pozitiv); PLANTAREA INVERSA (numai cheltuirile cu finantarea citita conteaza) da F si iese ROSIE', async () => {
|
|
const r = await ruleazaK12(AICI, k12.fara, 'fara'), rc = await ruleazaK12(AICI, k12.cu, 'cu');
|
|
const bun = r.cod === 2 && eticheta(r.raport, k12.adresa) === U_ && nemasuratCu(r.raport, k12.adresa, 'spend(s) of this address seen') && rc.cod === 0 && eticheta(rc.raport, k12.adresa) === E_;
|
|
if (!bun) return [false, `fara witness: ${detaliu(r.raport, k12.adresa).slice(0, 90)} | cu witness: ${detaliu(rc.raport, k12.adresa)}`];
|
|
const d = copie('k12'); if (planteaza(path.join(d, 'clasifica.mjs'), 'const spendsVazute = ins.filter((i) => i.spendsTarget || (!tipExpus && i.findings.length > 0));', 'const spendsVazute = spends;') !== 1) return 'tinta plantarii nu apare o data';
|
|
const p = await ruleazaK12(d, k12.fara, 'inv');
|
|
return [bun && eticheta(p.raport, k12.adresa) === F_, `${detaliu(r.raport, k12.adresa).slice(0, 90)} | cu witness: ${eticheta(rc.raport, k12.adresa)} | copia plantata: ${eticheta(p.raport, k12.adresa)}`];
|
|
});
|
|
// d. o iesire P2TR numai in MEMPOOL: cheia de iesire e publica acolo, dar nu pe lant -> NEMASURAT, nu F "no output exists yet"
|
|
const p2trMempool = async (dir) => { const k = cheie('p2tr-iesire-mempool'); const spk = Buffer.concat([Buffer.from([0x51, 0x20]), k.pub.subarray(1)]); const intrare = 'script:' + B.hex(spk); const f = finantare(spk, 'p2tr-iesire-mempool'); const m = await porneste('electrum', path.join(FIX, 'electrum.json'), ['--suprascrie', suprascriereElectrum('planta-p2tr-iesire-mempool', spk, [{ ...f, height: 0 }])]); const r = ruleaza({ dir, args: ['--mod', '2', '--adrese', scrieLista('p2tr-iesire-mempool', [intrare]), '--electrum', m.tinta], tinte: m.tinta }); m.opreste(); return { r, intrare }; };
|
|
await incearca('d. o iesire P2TR numai in MEMPOOL (Electrum height 0, nicio iesire confirmata): NEMASURAT numit ("public there but not yet on chain"), nu F "no output exists yet"; PLANTAREA INVERSA (regula scoasa) da F si face verificarea ROSIE', async () => {
|
|
const { r, intrare } = await p2trMempool(AICI);
|
|
const bun = r.cod === 2 && nemasuratCu(r.raport, intrare, 'in the target\'s mempool') && nemasuratCu(r.raport, intrare, 'not yet on chain');
|
|
if (!bun) return [false, `unealta a dat ${detaliu(r.raport, intrare).slice(0, 90)} (cod ${r.cod})`];
|
|
const d = copie('p2tr-iesire'); const n = planteaza(path.join(d, 'clasifica.mjs'), 'if (tipExpus && !outs.length && obs.unconfirmedOutputs > 0) return nem(', 'if (false) return nem(');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const p = await p2trMempool(d);
|
|
const prins = !(p.r.cod === 2 && nemasuratCu(p.r.raport, p.intrare, 'in the target\'s mempool')) && eticheta(p.r.raport, p.intrare) === F_;
|
|
return [bun && prins, `${detaliu(r.raport, intrare).slice(0, 90)} | copia plantata: ${detaliu(p.r.raport, p.intrare).slice(0, 40)}`];
|
|
});
|
|
// d. Esplora serveste CAMPURI, nu octeti: fiecare tranzactie e re-serializata si trebuie sa se hashuiasca la id-ul dat de index
|
|
const esploraId = (() => {
|
|
const k = cheie('esplora-id'); const h = B.hash160(k.pub); const spk = p2pkhSpk(h); const adresa = b58check(0, h);
|
|
const f = finantare(spk, 'esplora-id'); const s = cheltuire(f.id, Buffer.concat([push(SIG), push(k.pub)]), []);
|
|
const stats = { address: adresa, chain_stats: { funded_txo_count: 1, funded_txo_sum: 100000, spent_txo_count: 1, spent_txo_sum: 100000, tx_count: 2 }, mempool_stats: { funded_txo_count: 0, funded_txo_sum: 0, spent_txo_count: 0, spent_txo_sum: 0, tx_count: 0 } };
|
|
const fisier = (nume, schimba) => { const sp = esploraTx({ ...s, height: 2 }); schimba(sp); const fx = path.join(T, nume + '.json'); fs.writeFileSync(fx, JSON.stringify({ raspunsuri: { ['/address/' + adresa]: stats, ['/address/' + adresa + '/txs']: [sp, esploraTx({ ...f, height: 1 })] } })); return fx; };
|
|
return { adresa, idBun: s.id, exact: fisier('esplora-id-exact', () => {}), idSchimbat: fisier('esplora-id-schimbat', (x) => { x.txid = 'ab'.repeat(32); }), valoareSchimbata: fisier('esplora-id-valoare', (x) => { x.vout[0].value += 1; }) };
|
|
})();
|
|
const ruleazaEsploraId = async (dir, fx) => { const m = await porneste('esplora', path.join(FIX, 'esplora.json'), ['--suprascrie', fx]); const r = ruleaza({ dir, args: ['--mod', '2', '--adrese', scrieLista('esplora-id', [esploraId.adresa]), '--esplora', m.url], tinte: m.tinta }); m.opreste(); return r; };
|
|
await incearca('d. Esplora: tranzactiile servite EXACT -> E; cu id-ul cheltuirii inlocuit (ab..ab, campurile neschimbate) sau cu o valoare schimbata cu 1 satoshi (id-ul vechi) -> NEMASURAT ("does not hash to its id"), niciodata o dovada care citeaza un id inventat', async () => {
|
|
const r0 = await ruleazaEsploraId(AICI, esploraId.exact), r1 = await ruleazaEsploraId(AICI, esploraId.idSchimbat), r2 = await ruleazaEsploraId(AICI, esploraId.valoareSchimbata);
|
|
const a = esploraId.adresa;
|
|
return [r0.cod === 0 && eticheta(r0.raport, a) === E_ && dovada(r0.raport, a, esploraId.idBun) && r1.cod === 2 && nemasuratCu(r1.raport, a, 'does not hash to its id') && r2.cod === 2 && nemasuratCu(r2.raport, a, 'does not hash to its id'), `exact: ${detaliu(r0.raport, a)} | id schimbat: ${detaliu(r1.raport, a).slice(0, 70)} | valoare schimbata: ${detaliu(r2.raport, a).slice(0, 70)}`];
|
|
});
|
|
await incearca('d. PLANTAREA INVERSA: o copie in care Esplora nu mai compara id-ul re-serializat face verificarea de mai sus ROSIE (id-ul inventat ajunge in dovada)', async () => {
|
|
const d = copie('esplora-id'); const n = planteaza(path.join(d, 'adaptoare.mjs'), 'if (id.txid !== x.txid) return { partial:', 'if (false) return { partial:');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const r = await ruleazaEsploraId(d, esploraId.idSchimbat);
|
|
return [!(r.cod === 2 && nemasuratCu(r.raport, esploraId.adresa, 'does not hash to its id')) && dovada(r.raport, esploraId.adresa, 'ab'.repeat(32)), `copia plantata a dat ${detaliu(r.raport, esploraId.adresa).slice(0, 80)} (cod ${r.cod})`];
|
|
});
|
|
// e. eticheta interzisa
|
|
await incearca('e. eticheta interzisa plantata in limbaj.mjs al unei copii: poarta de limbaj REFUZA raportul (cod 1, nimic scris, cuvantul numit)', async () => {
|
|
const d = copie('limbaj'); const n = planteaza(path.join(d, 'limbaj.mjs'), "FARA_DOVADA: 'no on-chain evidence of exposure'", "FARA_DOVADA: 'safe'");
|
|
if (n !== 1) return `tinta apare de ${n} ori`;
|
|
const r = ruleaza({ dir: d, args: ['--mod', '1', '--adrese', LISTA_TOT] });
|
|
return [r.cod === 1 && !r.scris && /REFUSED \(language\)/.test(r.err) && /forbidden word/.test(r.err), r.err.slice(0, 120)];
|
|
});
|
|
await incearca('e. un --subiect dat de cerut care contine un cuvant interzis ("treasury safe 2026") NU e refuzat: e consemnat ca atare, poarta judeca textul uneltei', async () => {
|
|
const r = ruleaza({ args: ['--mod', '1', '--adrese', LISTA_BTC, '--subiect', 'treasury safe 2026'] });
|
|
return [r.cod === 0 && r.scris && r.raport.subject === 'treasury safe 2026' && /Subject: treasury safe 2026/.test(r.md), r.err.slice(0, 100) || 'cod ' + r.cod];
|
|
});
|
|
// e. textul SERVIT de tinta (un corp de eroare) nu e textul uneltei: e citat ca [served: ...] si poarta nu il judeca
|
|
const tintaCuvant = async (dir) => { const fx = path.join(T, 'planta-401.json'); fs.writeFileSync(fx, JSON.stringify({ raspunsuri: { ['/address/' + A.p2pkh_necheltuit]: { _status: 401, _corp: 'Authorization required: this endpoint is protected' } } })); const m = await porneste('esplora', path.join(FIX, 'esplora.json'), ['--suprascrie', fx]); const r = ruleaza({ dir, args: ['--mod', '2', '--adrese', scrieLista('unu-401', [A.p2pkh_necheltuit]), '--esplora', m.url], tinte: m.tinta }); m.opreste(); return r; };
|
|
await incearca('e. un corp de eroare SERVIT de tinta care contine un cuvant interzis (HTTP 401 "this endpoint is protected"): raportul e scris (cod 2, adresa NEMASURAT, corpul citat ca [served: ...]); PLANTAREA INVERSA (citatul judecat ca text al uneltei) il refuza si face verificarea ROSIE', async () => {
|
|
const r = await tintaCuvant(AICI);
|
|
const bun = r.cod === 2 && r.scris && nemasuratCu(r.raport, A.p2pkh_necheltuit, '[served: Authorization required: this endpoint is protected]');
|
|
if (!bun) return [false, `unealta a dat cod ${r.cod}: ${(r.raport ? detaliu(r.raport, A.p2pkh_necheltuit) : r.err).slice(0, 110)}`];
|
|
const d = copie('servit'); const n = planteaza(path.join(d, 'raport.mjs'), ", '[served: ]')", ', (m) => m)');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const p = await tintaCuvant(d);
|
|
return [bun && p.cod === 1 && !p.scris && /REFUSED \(language\)/.test(p.err), `${r.cod}: ${detaliu(r.raport, A.p2pkh_necheltuit).slice(0, 90)} | copia plantata: cod ${p.cod} ${p.err.slice(0, 50)}`];
|
|
});
|
|
// e. un IDENTIFICATOR servit care nu e unul (un tx_hash, un txid, o pozitie) si un mesaj servit taiat la 200 de caractere sunt tot textul
|
|
// tintei: fiecare e citat ca [served: ...] (sau refuza raspunsul), si raportul se scrie; o plantare per drum (validarea scoasa) il
|
|
// lasa necitat, iar poarta de limbaj refuza tot raportul (cod 1, "REFUSED (language)"), ceea ce face verificarea ROSIE
|
|
const servitCazuri = (() => {
|
|
const k = cheie('servit'); const h = B.hash160(k.pub); const spk = p2pkhSpk(h); const adresa = b58check(0, h); const f = finantare(spk, 'servit');
|
|
const kt = cheie('servit-p2tr'); const spkTr = Buffer.concat([Buffer.from([0x51, 0x20]), kt.pub.subarray(1)]); const intrareTr = 'script:' + B.hex(spkTr);
|
|
const fisier = (nume, raspunsuri) => { const fx = path.join(T, 'servit-' + nume + '.json'); fs.writeFileSync(fx, JSON.stringify({ raspunsuri })); return fx; };
|
|
const sh = B.scripthash(spk);
|
|
const hc = istoricCore(A.p2pkh_cheltuit); const vSpend = bitcoindFix.raspunsuri['getrawtransaction:' + hc.chelt[0]];
|
|
return [
|
|
{ nume: 'Electrum tx_hash', fel: 'electrum', adrese: [adresa], cuvant: 'safe protected', fx: fisier('el-txhash', { ['blockchain.scripthash.get_history:' + sh]: [{ tx_hash: 'safe protected', height: 1 }], ['blockchain.transaction.get:safe protected']: f.raw.toString('hex') }), cod: 2,
|
|
planta: [['adaptoare.mjs', 'if (!ID_BTC.test(h.tx_hash)) return', 'if (false) return']] },
|
|
{ nume: 'Esplora txid', fel: 'esplora', adrese: [adresa], cuvant: 'trustless', fx: fisier('es-txid', { ['/address/' + adresa]: { address: adresa, chain_stats: { funded_txo_count: 1, funded_txo_sum: 100000, spent_txo_count: 0, spent_txo_sum: 0, tx_count: 1 }, mempool_stats: { funded_txo_count: 0, funded_txo_sum: 0, spent_txo_count: 0, spent_txo_sum: 0, tx_count: 0 } }, ['/address/' + adresa + '/txs']: [esploraTx({ ...f, height: 1, txid: 'trustless' })] }), cod: 2,
|
|
planta: [['adaptoare.mjs', 'if (rau !== undefined) throw', 'if (false) throw']] },
|
|
{ nume: 'Core scantxoutset txid', fel: 'bitcoind', adrese: [intrareTr], cuvant: 'guaranteed', fx: fisier('core-scan', { ['scantxoutset:raw(' + B.hex(spkTr) + ')']: { success: true, unspents: [{ txid: 'guaranteed', vout: 0, scriptPubKey: B.hex(spkTr), desc: 'raw(' + B.hex(spkTr) + ')', amount: 0.001, height: 5 }] } }), cod: 2,
|
|
planta: [['adaptoare.mjs', 'if (uRau) return', 'if (false) return']] },
|
|
{ nume: 'Core blockhash', fel: 'bitcoind', adrese: [A.p2pkh_cheltuit], txids: [...hc.fin, ...hc.chelt], cuvant: 'safe', fx: fisier('core-bloc', { ['getrawtransaction:' + hc.chelt[0]]: { ...vSpend, blockhash: 'safe' } }), cod: 1, refuz: /REFUSED \(txids\)/,
|
|
planta: [['adaptoare.mjs', "if (v.blockhash !== undefined && v.blockhash !== null && !ID_BTC.test(String(v.blockhash))) throw", 'if (false) throw']] },
|
|
{ nume: 'mesaj servit taiat la 200 de caractere', fel: 'electrum', adrese: [adresa], cuvant: 'protected', fx: fisier('el-taiat', { ['blockchain.scripthash.get_history:' + sh]: { _eroare: { code: -32000, message: 'protected ' + 'x'.repeat(190) } } }), cod: 2,
|
|
planta: [['adaptoare.mjs', "if (x.length > 200) { x = x.slice(0, 200); const i = x.lastIndexOf('[served: '); if (i >= 0 && x.indexOf(']', i) < 0) x += ']'; } return x;", 'return x.slice(0, 200);'], ['raport.mjs', '(?:\\]|$)', '\\]']] },
|
|
];
|
|
})();
|
|
const ruleazaServit = async (dir, c) => { const m = await porneste(c.fel, path.join(FIX, c.fel === 'electrum' ? 'electrum.json' : c.fel === 'esplora' ? 'esplora.json' : 'bitcoind.json'), ['--suprascrie', c.fx]); const r = ruleaza({ dir, args: ['--mod', '2', '--adrese', scrieLista('servit-' + c.nume.replace(/\W+/g, '-'), c.adrese), c.fel === 'electrum' ? '--electrum' : '--' + c.fel, c.fel === 'electrum' ? m.tinta : m.url, ...(c.txids ? ['--txids', scrieLista('servit-txids-' + c.nume.replace(/\W+/g, '-'), c.txids)] : [])], tinte: m.tinta }); m.opreste(); return r; };
|
|
// raportul e scris si adresa NEMASURAT cu textul tintei CITAT (sau, pentru un raspuns la --txids, refuzul numit, nu cel de limbaj)
|
|
const servitBun = (r, c) => (c.refuz ? r.cod === 1 && !r.scris && c.refuz.test(r.err) && !/REFUSED \(language\)/.test(r.err) && r.err.includes('[served: ' + c.cuvant) : r.cod === c.cod && r.scris && c.adrese.every((a) => eticheta(r.raport, a) === U_ && nemasuratCu(r.raport, a, '[served: ')) && JSON.stringify(r.raport).includes(c.cuvant));
|
|
for (const c of servitCazuri) {
|
|
await incearca(`e. textul tintei citat: ${c.nume} cu "${c.cuvant}" -> ${c.refuz ? 'refuzul raspunsului numit (nu cel de limbaj)' : 'raport scris, adresa NEMASURAT, textul citat [served: ...]'}; PLANTAREA INVERSA (citarea scoasa) e refuzata de poarta de limbaj si iese ROSIE`, async () => {
|
|
const r = await ruleazaServit(AICI, c);
|
|
const bun = servitBun(r, c);
|
|
if (!bun) return [false, `unealta a dat cod ${r.cod}: ${(r.raport ? detaliu(r.raport, c.adrese[0]) : r.err).slice(0, 120)}`];
|
|
const d = copie('servit-' + c.nume.replace(/\W+/g, '-').toLowerCase());
|
|
for (const [f, vechi, nou] of c.planta) { const n = planteaza(path.join(d, f), vechi, nou); if (n !== 1) return `tinta plantarii in ${f} apare de ${n} ori`; }
|
|
const p = await ruleazaServit(d, c);
|
|
return [bun && !servitBun(p, c) && p.cod === 1 && /REFUSED \(language\)/.test(p.err), `${(r.raport ? detaliu(r.raport, c.adrese[0]) : r.err.replace(/[0-9a-f]{64}/g, (x) => x.slice(0, 8) + '..')).slice(0, 90)} | copia plantata: cod ${p.cod} ${p.err.slice(0, 40)}`];
|
|
});
|
|
}
|
|
// f. modul 3
|
|
{
|
|
const r0 = ruleaza({ args: ['--mod', '3', '--adrese', LISTA_BTC, '--esplora', 'http://127.0.0.1:9/'] });
|
|
cer('f. modul 3 fara consimtamant scris: REFUZAT (cod 1), nimic scris, nicio conexiune', da(r0.cod === 1 && !r0.scris && /REFUSED \(consent\)/.test(r0.err) && !!r0.jurnal && r0.jurnal.conexiuni.length === 0), r0.err.slice(0, 100));
|
|
const m = await porneste('esplora', path.join(FIX, 'esplora.json'));
|
|
const r1 = ruleaza({ args: ['--mod', '3', '--adrese', LISTA_BTC, '--esplora', m.url, '--consimtamant-tert', 'I consent to sending these addresses to a third-party provider'], tinte: m.tinta });
|
|
await sleep(100); const c = m.contor(); m.opreste();
|
|
cer('f. modul 3 cu consimtamant spre o tinta PRIVATA (loopback): REFUZAT ca tinta privata, zero conexiuni acceptate de macheta', da(r1.cod === 1 && !r1.scris && /target-private/.test(r1.err) && c.connections === 0 && !!r1.jurnal && r1.jurnal.conexiuni.length === 0), r1.err.slice(0, 100));
|
|
const r6 = ruleaza({ args: ['--mod', '3', '--adrese', LISTA_BTC, '--esplora', 'https://[2001:db8::1]/', '--consimtamant-tert', 'I consent to sending these addresses to a third-party provider'] });
|
|
cer('f. modul 3 spre un literal IPv6 de documentatie: REFUZAT ca tinta privata (nu "does not resolve"), nicio conexiune', da(r6.cod === 1 && /target-private/.test(r6.err) && !!r6.jurnal && r6.jurnal.conexiuni.length === 0 && r6.jurnal.dns.length === 0), r6.err.slice(0, 100));
|
|
cer('f. modul 3 catre un furnizor real', 'NEMASURAT', 'ar trimite adrese unui tert; nu se ruleaza in proba fara consimtamant');
|
|
const r2 = ruleaza({ args: ['--mod', '1', '--adrese', LISTA_BTC, '--esplora', 'http://127.0.0.1:9/'] });
|
|
cer('f. modul 1 cu o sursa de date data: REFUZAT (modul 1 e offline prin definitie)', da(r2.cod === 1 && !r2.scris && /offline/.test(r2.err)));
|
|
}
|
|
// g. raport / plic schimbate
|
|
if (m1 && m1.raport) {
|
|
const schimbat = JSON.parse(JSON.stringify(m1.raport)); schimbat.addresses[0].label = F_;
|
|
const plic = JSON.parse(fs.readFileSync(m1.plic, 'utf8'));
|
|
cer('g. raport.json cu o eticheta schimbata dupa semnare: verificaRaport REFUZA (amprenta != evidenceHash)', da(verificaRaport(schimbat, plic).ok === false));
|
|
const plicRau = JSON.parse(JSON.stringify(plic)); plicRau.statement.result = plicRau.statement.result.replace(/\d+ exposed/, '0 exposed');
|
|
const f = path.join(T, 'plic-rau.json'); fs.writeFileSync(f, JSON.stringify(plicRau));
|
|
cerVerif('g. plic cu rezultatul schimbat: verificatorul de referinta spune INVALID', da(verdictVerificator(f) === 'INVALID'));
|
|
} else cer('g. raport / plic schimbate', 'STRICAT', 'modul 1 nu a produs un raport');
|
|
// h. dovezi care NU sunt ale tintei
|
|
await incearca('h. o intrare care cheltuie ALTA iesire taproot nu e dovada pentru tinta P2TR; aceeasi intrare, daca ar cheltui tinta, ar fi', async () => {
|
|
const t = B.decodeazaIntrareBitcoin(A.p2tr_nefinantat);
|
|
const alt = Object.values(txs).find((tx) => tx.vin.some((v) => v.witness.length >= 2 && !tx.vout.some((o) => false) && B.dovezileIntrarii(t, v, true).some((f) => f.what === 'taproot-internal-key-revealed')));
|
|
if (!alt) { const sint = { scriptSig: Buffer.alloc(0), witness: [Buffer.alloc(64, 1), Buffer.from([0x51]), Buffer.concat([Buffer.from([0xc0]), Buffer.alloc(32, 2)])] }; return [B.dovezileIntrarii(t, sint, false).length === 0 && B.dovezileIntrarii(t, sint, true).length === 1, 'pe o intrare sintetica de cale de script']; }
|
|
const vin = alt.vin.find((v) => B.dovezileIntrarii(t, v, true).length);
|
|
return [B.dovezileIntrarii(t, vin, false).length === 0 && B.dovezileIntrarii(t, vin, true).length === 1, 'pe o intrare reala din fixtura'];
|
|
});
|
|
await incearca('h. cheia dezvaluita in cheltuirea unei adrese P2PKH nu e dovada pentru ALTA adresa P2PKH (HASH160 diferit)', async () => {
|
|
const tS = B.decodeazaIntrareBitcoin(A.p2pkh_cheltuit), tN = B.decodeazaIntrareBitcoin(A.p2pkh_necheltuit);
|
|
const spend = Object.values(txs).find((tx) => tx.vin.some((v) => B.dovezileIntrarii(tS, v, true).some((f) => f.what === 'public-key-revealed')));
|
|
if (!spend) return 'nicio cheltuire a adresei P2PKH cheltuite in fixtura';
|
|
return spend.vin.every((v) => B.dovezileIntrarii(tN, v, true).length === 0);
|
|
});
|
|
// i. Core fara --txids: plantarea inversa. Plasa de la capat (prin Core niciodata F, verificata separat mai jos) ar ascunde ce face
|
|
// fiecare garda in parte, deci plantarile pe o garda Core o scot si pe ea: fiecare arata ca F-ul se intoarce fara garda ei
|
|
const PLASA_CORE = 'if (obs.historyFromRequesterList && (tipExpus ? !outs.length : !revel.length)) return nem(';
|
|
// din runda 6 o a doua plasa tine si ea: fara un varf citit (Core nu citeste niciunul) nu se da F; plantarea le scoate pe amandoua
|
|
const PLASA_VARF = "} else if (r.label === ETICHETA.FARA_DOVADA) r = nem(r, 'not measured: the tip of the target was not read";
|
|
// din runda 7: indexul numara in mempool mai multe tranzactii ale scriptului decat a purtat pagina -> NEMASURAT (plasa regulilor mempool)
|
|
const PLASA_TX_MEMPOOL = 'if (!revel.length && !expusPrinTip && obs.mempoolTxNotServed) return nem(';
|
|
const faraPlasaCore = (d) => (planteaza(path.join(d, 'clasifica.mjs'), PLASA_CORE, 'if (false) return nem(') === 1 && planteaza(path.join(d, 'clasifica.mjs'), PLASA_VARF, "} else if (false) r = nem(r, 'not measured: the tip of the target was not read") === 1 ? 1 : 0);
|
|
await incearca('i. PLANTAREA INVERSA: o copie in care scanul Core pretinde ca a enumerat cheltuirile (si fara plasa de la capat) face verificarea "Core fara --txids" ROSIE', async () => {
|
|
const d = copie('core'); const n = planteaza(path.join(d, 'adaptoare.mjs'), 'obs.spendsNotEnumerable = nTxids === 0 || !atinsa;', 'obs.spendsNotEnumerable = false;');
|
|
if (n !== 1 || faraPlasaCore(d) !== 1) return `tinta plantarii apare de ${n} ori (sau plasa nu apare o data)`;
|
|
const m = await porneste('bitcoind', path.join(FIX, 'bitcoind.json'));
|
|
const r = ruleaza({ dir: d, args: ['--mod', '2', '--adrese', LISTA_BTC, '--bitcoind', m.url], tinte: m.tinta }); m.opreste();
|
|
return [predicatCoreFaraTxids(r) === false && !!r.raport && r.raport.summary.noOnChainEvidence > 0, `copia plantata a dat ${r.raport ? r.raport.summary.verdict : 'niciun raport'}`];
|
|
});
|
|
// i. Core cu --txids INCOMPLET: numai finantarile unei adrese cheltuite; scanul spune 0 necheltuite -> cheltuirea NU a fost citita
|
|
const predicatCoreIncomplet = (r, adresa) => r.cod === 2 && nemasuratCu(r.raport, adresa, 'not among the --txids') && r.raport.summary.noOnChainEvidence === 0;
|
|
await incearca('i. Core cu --txids INCOMPLET (numai finantarile adresei P2PKH cheltuite; scanul da 0 necheltuite): NEMASURAT numit ("not among the --txids given"), niciodata F; cu lista COMPLETA aceeasi adresa e E (controlul pozitiv)', async () => {
|
|
const h = istoricCore(A.p2pkh_cheltuit); if (!h.fin.length || !h.chelt.length) return `istoricul din fixtura nu are finantari (${h.fin.length}) si cheltuiri (${h.chelt.length})`;
|
|
const lista = scrieLista('unu-core', [A.p2pkh_cheltuit]);
|
|
const m1 = await porneste('bitcoind', path.join(FIX, 'bitcoind.json')); const r1 = ruleaza({ args: ['--mod', '2', '--adrese', lista, '--bitcoind', m1.url, '--txids', scrieLista('txids-fin', h.fin)], tinte: m1.tinta }); m1.opreste();
|
|
const m2 = await porneste('bitcoind', path.join(FIX, 'bitcoind.json')); const r2 = ruleaza({ args: ['--mod', '2', '--adrese', lista, '--bitcoind', m2.url, '--txids', scrieLista('txids-tot', [...h.fin, ...h.chelt])], tinte: m2.tinta }); m2.opreste();
|
|
return [predicatCoreIncomplet(r1, A.p2pkh_cheltuit) && r2.cod === 0 && eticheta(r2.raport, A.p2pkh_cheltuit) === E_, `incomplet (${h.fin.length} txids): ${detaliu(r1.raport, A.p2pkh_cheltuit).slice(0, 90)} | complet (${h.fin.length + h.chelt.length}): ${detaliu(r2.raport, A.p2pkh_cheltuit)}`];
|
|
});
|
|
await incearca('i. PLANTAREA INVERSA: o copie in care numaratoarea iesirilor cheltuite necitite e fortata la 0 (si fara plasa de la capat) face verificarea "Core cu --txids incomplet" ROSIE', async () => {
|
|
const d = copie('core2'); const n = planteaza(path.join(d, 'adaptoare.mjs'), "obs.spentNotRead = obs.outputs.filter((x) => !unspentSet.has(x.txid + ':' + x.vout) && !spentRead.has(x.txid + ':' + x.vout)).length;", 'obs.spentNotRead = 0;');
|
|
if (n !== 1 || faraPlasaCore(d) !== 1) return `tinta plantarii apare de ${n} ori (sau plasa nu apare o data)`;
|
|
const h = istoricCore(A.p2pkh_cheltuit);
|
|
const m = await porneste('bitcoind', path.join(FIX, 'bitcoind.json')); const r = ruleaza({ dir: d, args: ['--mod', '2', '--adrese', scrieLista('unu-core-inv', [A.p2pkh_cheltuit]), '--bitcoind', m.url, '--txids', scrieLista('txids-fin-inv', h.fin)], tinte: m.tinta }); m.opreste();
|
|
return [predicatCoreIncomplet(r, A.p2pkh_cheltuit) === false && !!r.raport && r.raport.summary.noOnChainEvidence > 0, `copia plantata a dat ${r.raport ? r.raport.summary.verdict : 'niciun raport'}`];
|
|
});
|
|
// i. Core cu --txids care NU ating o adresa: lista unui custode spune ceva numai despre adresele pe care le atinge; pentru celelalte,
|
|
// informatia e aceeasi ca fara --txids (scanul vede numai iesirile necheltuite), deci la fel NEMASURAT
|
|
const ids1 = (() => { const h = istoricCore(A.p2pkh_cheltuit); return [...h.fin, ...h.chelt]; })();
|
|
const neatinse1 = ROLURI_BTC.filter((rol) => { const x = istoricCore(A[rol]); return ![...x.fin, ...x.chelt].some((id) => ids1.includes(id)); });
|
|
const coreLista1 = async (dir) => { const m = await porneste('bitcoind', path.join(FIX, 'bitcoind.json')); const r = ruleaza({ dir, args: ['--mod', '2', '--adrese', LISTA_BTC, '--bitcoind', m.url, '--txids', scrieLista('txids-c1-' + path.basename(dir), ids1)], tinte: m.tinta }); m.opreste(); return r; };
|
|
const predicatNeatinse = (r) => !!r.raport && r.raport.summary.noOnChainEvidence === 0 && eticheta(r.raport, A.p2pkh_cheltuit) === E_ && neatinse1.every((rol) => (eticheta(r.raport, A[rol]) === U_ && nemasuratCu(r.raport, A[rol], `none of the ${ids1.length} transaction(s) given with --txids touches this address`)) || (eticheta(r.raport, A[rol]) === E_ && (rol === 'p2pk' || rol.startsWith('p2tr')) && necheltuite(rol) > 0));
|
|
await incearca('i. Core cu --txids = istoricul COMPLET al adresei P2PKH cheltuite, pe cei 8 vectori: zero F; fiecare adresa pe care nicio tranzactie data nu o atinge iese NEMASURAT numit ("none of the N transaction(s) given with --txids touches this address") sau E din scan (cheia in iesire); adresa atinsa iese E', async () => {
|
|
if (neatinse1.length < 5) return `fixtura nu are destule adrese neatinse de lista (${neatinse1.length})`;
|
|
const r = await coreLista1(AICI);
|
|
return [r.cod === 2 && predicatNeatinse(r), `${ids1.length} txids, neatinse ${neatinse1.length}: ${neatinse1.map((rol) => rol + '=' + eticheta(r.raport, A[rol])).join(' ')}`];
|
|
});
|
|
await incearca('i. PLANTAREA INVERSA: o copie cu steagul GLOBAL (o lista data pentru o adresa "enumera" cheltuirile tuturor; si fara plasa de la capat) da F pe adresele neatinse si face verificarea de mai sus ROSIE', async () => {
|
|
const d = copie('core-atinsa'); const n = planteaza(path.join(d, 'adaptoare.mjs'), 'obs.spendsNotEnumerable = nTxids === 0 || !atinsa;', 'obs.spendsNotEnumerable = nTxids === 0;');
|
|
if (n !== 1 || faraPlasaCore(d) !== 1) return `tinta plantarii apare de ${n} ori (sau plasa nu apare o data)`;
|
|
const r = await coreLista1(d);
|
|
return [predicatNeatinse(r) === false && !!r.raport && r.raport.summary.noOnChainEvidence > 0, `copia plantata a dat ${r.raport ? r.raport.summary.verdict : 'niciun raport'}`];
|
|
});
|
|
await incearca('i. un txid pe care nodul NU il serveste in --txids: REFUZ numit (cod 1, nimic scris, id-ul INTREG in mesaj si niciun alt hex lung), inainte de orice masuratoare (cereri servite <= 3 pentru lantul nodului + txids, zero scanuri)', async () => {
|
|
const h = istoricCore(A.p2pkh_cheltuit); const rau = 'ff'.repeat(32);
|
|
// in capitale in fisier: acelasi id (se compara cu id-urile calculate din octeti, in minuscule)
|
|
const m = await porneste('bitcoind', path.join(FIX, 'bitcoind.json')); const r = ruleaza({ args: ['--mod', '2', '--adrese', LISTA_BTC, '--bitcoind', m.url, '--txids', scrieLista('txids-rau', [...h.fin, rau.toUpperCase()])], tinte: m.tinta }); await sleep(100); const c = m.contor(); m.opreste();
|
|
const hexLung = r.err.match(/[0-9a-fA-F]{40,}/g) || [];
|
|
return [r.cod === 1 && !r.scris && /REFUSED \(txids\)/.test(r.err) && r.err.includes(rau) && hexLung.every((x) => x.toLowerCase() === rau) && c.requests <= 3 + h.fin.length + 1 && c.unknown.every((k) => k.startsWith('getrawtransaction:')), `cod ${r.cod}, cereri servite ${c.requests} (txids ${h.fin.length + 1}), hex lung in mesaj ${hexLung.length}: ${r.err.replace(/[0-9a-f]{64}/g, (x) => x.slice(0, 8) + '..').slice(0, 110)}`];
|
|
});
|
|
await incearca('i. drumul FARA hex (getrawtransaction cu forma decodata vin/vout, cum raspunde un nod fara hex; tranzactia RE-SERIALIZATA din ea si hashuita la id): aceleasi etichete E pe P2WPKH, P2SH multisig si P2WSH multisig; un camp schimbat in forma decodata (o valoare) -> REFUZ "hashes to"', async () => {
|
|
const roluri = ['p2wpkh_cheltuit', 'p2sh_multisig_cheltuit', 'p2wsh_multisig_cheltuit']; const ids = [], adrese = [];
|
|
for (const rol of roluri) { const h = istoricCore(A[rol]); ids.push(...h.fin, ...h.chelt); adrese.push(A[rol]); }
|
|
const sup = {}; for (const id of new Set(ids)) { const v = bitcoindFix.raspunsuri['getrawtransaction:' + id]; if (!v || !v.vin || !v.vout) return `fixtura nu are forma decodata pentru ${id.slice(0, 12)}`; const { hex, ...fara } = v; sup['getrawtransaction:' + id] = fara; }
|
|
const f = path.join(T, 'planta-fara-hex.json'); fs.writeFileSync(f, JSON.stringify({ raspunsuri: sup }));
|
|
const m = await porneste('bitcoind', path.join(FIX, 'bitcoind.json'), ['--suprascrie', f]); const r = ruleaza({ args: ['--mod', '2', '--adrese', scrieLista('trei-fara-hex', adrese), '--bitcoind', m.url, '--txids', scrieLista('txids-fara-hex', [...new Set(ids)])], tinte: m.tinta }); m.opreste();
|
|
const bune = r.cod === 0 && adrese.every((a) => eticheta(r.raport, a) === E_) && dovada(r.raport, A.p2wpkh_cheltuit, 'public key revealed in the witness') && [A.p2sh_multisig_cheltuit, A.p2wsh_multisig_cheltuit].every((a) => gaseste(r.raport, a).evidence.some((e) => /\d+-of-\d+ multisig/.test(e)));
|
|
// controlul negativ: valoarea primei iesiri a unei tranzactii schimbata cu un satoshi in forma decodata -> alt id -> refuz
|
|
const idSchimbat = [...new Set(ids)][0]; const sup2 = JSON.parse(JSON.stringify(sup)); const vs = sup2['getrawtransaction:' + idSchimbat]; vs.vout[0].value = Number((Number(vs.vout[0].value) + 0.00000001).toFixed(8));
|
|
const f2 = path.join(T, 'planta-fara-hex-rau.json'); fs.writeFileSync(f2, JSON.stringify({ raspunsuri: sup2 }));
|
|
const m2 = await porneste('bitcoind', path.join(FIX, 'bitcoind.json'), ['--suprascrie', f2]); const r2 = ruleaza({ args: ['--mod', '2', '--adrese', scrieLista('trei-fara-hex-rau', adrese), '--bitcoind', m2.url, '--txids', scrieLista('txids-fara-hex-rau', [...new Set(ids)])], tinte: m2.tinta }); m2.opreste();
|
|
const refuz = r2.cod === 1 && !r2.scris && /REFUSED \(txids\)/.test(r2.err) && /without its hex/.test(r2.err) && /hashes to/.test(r2.err) && r2.err.includes(idSchimbat);
|
|
return [bune && refuz, `${new Set(ids).size} tranzactii fara hex: ${adrese.map((a) => eticheta(r.raport, a)).join(', ')} | o valoare schimbata: cod ${r2.cod} ${r2.err.replace(/[0-9a-f]{64}/g, (x) => x.slice(0, 8) + '..').slice(0, 90)}`];
|
|
});
|
|
// i. Core: o tranzactie data care atinge adresa NUMAI in mempool nu e o atingere pe lant. Adresa X (P2WPKH) a fost finantata si
|
|
// cheltuita PE LANT (cheltuirea dezvaluie cheia; nu e data), scanul nu ii gaseste iesiri; --txids = [M], o plata catre X numai in mempool
|
|
const coreSintetic = async (dir, nume, raspunsuri, adrese, txids) => { const f = path.join(T, 'core-' + nume + '.json'); fs.writeFileSync(f, JSON.stringify({ raspunsuri })); const m = await porneste('bitcoind', path.join(FIX, 'bitcoind.json'), ['--suprascrie', f]); const r = ruleaza({ dir, args: ['--mod', '2', '--adrese', scrieLista('core-' + nume, adrese), '--bitcoind', m.url, ...(txids.length ? ['--txids', scrieLista('txids-' + nume, txids)] : [])], tinte: m.tinta }); m.opreste(); return r; };
|
|
const blocSintetic = (et) => crypto.createHash('sha256').update('bloc sintetic ' + et).digest('hex');
|
|
// forma verbose a lui getrawtransaction: blockhash + confirmations >= 1 pe lantul ACTIV, confirmations 0 intr-un bloc din afara lui
|
|
// (runda 7) cu blocktime: ora blocului, judecata fata de data raportului; implicit 2026-10-11T21:17:30Z, cea mai noua din fixtura Core
|
|
const verbose = (x, bloc, confirmari = 1, blocktime = 1791753450) => ({ hex: x.raw.toString('hex'), txid: x.id, ...(bloc ? { blockhash: bloc, confirmations: confirmari, ...(blocktime === null ? {} : { blocktime }) } : {}) });
|
|
const k9 = (() => {
|
|
const k = cheie('k9'); const spk = Buffer.concat([Buffer.from([0x00, 0x14]), B.hash160(k.pub)]); const intrare = 'script:' + B.hex(spk);
|
|
const m = finantare(spk, 'k9-mempool'); const strain = finantare(p2pkhSpk(B.hash160(cheie('k9-strain').pub)), 'k9-strain');
|
|
const scan = { ['scantxoutset:raw(' + B.hex(spk) + ')']: { success: true, unspents: [] } };
|
|
return { intrare, m, strain, mempool: { ...scan, ['getrawtransaction:' + m.id]: verbose(m, null) }, ref: { ...scan, ['getrawtransaction:' + strain.id]: verbose(strain, blocSintetic('k9')) } };
|
|
})();
|
|
const ATINSA = 'const atinsa = obs.outputs.length > 0 || obs.inputs.length > 0;';
|
|
const atinsaCuMempool = (d) => planteaza(path.join(d, 'adaptoare.mjs'), ATINSA, 'const atinsa = obs.outputs.length > 0 || obs.inputs.length > 0 || obs.unconfirmedOutputs > 0 || obs.unconfirmedInputs > 0;');
|
|
const predicatK9 = (r) => !!r.raport && eticheta(r.raport, k9.intrare) === U_ && nemasuratCu(r.raport, k9.intrare, 'none of the 1 transaction(s) given with --txids touches this address on chain') && nemasuratCu(r.raport, k9.intrare, 'only in its mempool');
|
|
await incearca('i. Core: o lista care atinge adresa NUMAI printr-o tranzactie din MEMPOOL (adresa cheltuita pe lant, cheltuirea nedata, scanul fara iesiri) -> NEMASURAT numit ("touches this address on chain", atingerea din mempool numita), niciodata F; lista cu o tranzactie straina CONFIRMATA -> acelasi motiv, fara mempool (controlul pozitiv); PLANTAREA INVERSA (atingerea socoteste mempool) iese ROSIE, iar cu plasa de la capat scoasa si ea da chiar F', async () => {
|
|
const r1 = await coreSintetic(AICI, 'k9', k9.mempool, [k9.intrare], [k9.m.id]);
|
|
const r2 = await coreSintetic(AICI, 'k9-ref', k9.ref, [k9.intrare], [k9.strain.id]);
|
|
const bun = predicatK9(r1) && r1.cod === 2 && eticheta(r2.raport, k9.intrare) === U_ && nemasuratCu(r2.raport, k9.intrare, 'touches this address on chain') && !nemasuratCu(r2.raport, k9.intrare, 'mempool');
|
|
if (!bun) return [false, `mempool: ${detaliu(r1.raport, k9.intrare).slice(0, 110)} | strain: ${detaliu(r2.raport, k9.intrare).slice(0, 80)}`];
|
|
const d1 = copie('k9'); if (atinsaCuMempool(d1) !== 1) return 'tinta plantarii (atinsa) nu apare o data';
|
|
const p1 = await coreSintetic(d1, 'k9-inv', k9.mempool, [k9.intrare], [k9.m.id]);
|
|
const d2 = copie('k9-f'); if (atinsaCuMempool(d2) !== 1 || faraPlasaCore(d2) !== 1) return 'tintele plantarii (atinsa, plasa) nu apar o data';
|
|
const p2 = await coreSintetic(d2, 'k9-inv-f', k9.mempool, [k9.intrare], [k9.m.id]);
|
|
return [bun && !predicatK9(p1) && !predicatK9(p2) && eticheta(p2.raport, k9.intrare) === F_, `${detaliu(r1.raport, k9.intrare).slice(0, 100)} | atingerea cu mempool: ${eticheta(p1.raport, k9.intrare)} | si fara plasa: ${eticheta(p2.raport, k9.intrare)}`];
|
|
});
|
|
// i. Core: o tranzactie data care numeste un bloc din AFARA lantului activ (confirmations 0: un bloc vechi pe care txindex inca il
|
|
// numeste) nu e pe lant, exact ca una din mempool. Adresa P2PKH: finantarea pe lantul activ, cheltuirea (cu cheia) numai intr-un astfel
|
|
// de bloc -> NEMASURAT numit ("block off its active chain"), nu E "on-chain evidence"; aceeasi cheltuire pe lantul activ -> E
|
|
const kStale = (() => {
|
|
const k = cheie('k-stale'); const h = B.hash160(k.pub); const spk = p2pkhSpk(h); const adresa = b58check(0, h);
|
|
const f = finantare(spk, 'k-stale'); const s = cheltuire(f.id, Buffer.concat([push(SIG), push(k.pub)]), []);
|
|
const baza = { ['scantxoutset:' + adresa]: { success: true, unspents: [] }, ['getrawtransaction:' + f.id]: verbose(f, blocSintetic('k-stale-f'), 7) };
|
|
return { adresa, ids: [f.id, s.id], vechi: { ...baza, ['getrawtransaction:' + s.id]: verbose(s, blocSintetic('k-stale-s'), 0) }, activ: { ...baza, ['getrawtransaction:' + s.id]: verbose(s, blocSintetic('k-stale-s'), 3) } };
|
|
})();
|
|
await incearca('i. Core: cheltuirea care dezvaluie cheia e intr-un bloc din AFARA lantului activ (blockhash, confirmations 0) -> NEMASURAT numit ("block off its active chain"), nu E "on-chain evidence"; pe lantul activ (confirmations 3) -> E (controlul pozitiv); PLANTAREA INVERSA (pe lant = are blockhash) da E si iese ROSIE', async () => {
|
|
const r = await coreSintetic(AICI, 'k-stale', kStale.vechi, [kStale.adresa], kStale.ids);
|
|
const ra = await coreSintetic(AICI, 'k-stale-activ', kStale.activ, [kStale.adresa], kStale.ids);
|
|
const bun = r.cod === 2 && eticheta(r.raport, kStale.adresa) === U_ && nemasuratCu(r.raport, kStale.adresa, 'block off its active chain') && eticheta(ra.raport, kStale.adresa) === E_;
|
|
if (!bun) return [false, `bloc vechi: ${detaliu(r.raport, kStale.adresa).slice(0, 100)} | lant activ: ${detaliu(ra.raport, kStale.adresa)}`];
|
|
const d = copie('k-stale'); if (planteaza(path.join(d, 'adaptoare.mjs'), "const peLant = typeof v.blockhash === 'string' && Number.isInteger(v.confirmations) && v.confirmations >= 1;", "const peLant = typeof v.blockhash === 'string';") !== 1) return 'tinta plantarii nu apare o data';
|
|
const p = await coreSintetic(d, 'k-stale-inv', kStale.vechi, [kStale.adresa], kStale.ids);
|
|
return [bun && eticheta(p.raport, kStale.adresa) === E_, `${detaliu(r.raport, kStale.adresa).slice(0, 90)} | lant activ: ${eticheta(ra.raport, kStale.adresa)} | copia plantata: ${eticheta(p.raport, kStale.adresa)}`];
|
|
});
|
|
// i. plasa de la capat: prin Core istoricul unei adrese e lista data, pe care niciun raspuns al nodului nu o arata completa -> niciodata F
|
|
const k4 = (() => {
|
|
const k = cheie('k4'); const h = B.hash160(k.pub); const spk = p2pkhSpk(h); const adresa = b58check(0, h); const f = finantare(spk, 'k4');
|
|
return { adresa, f, raspunsuri: { ['scantxoutset:' + adresa]: { success: true, unspents: [{ txid: f.id, vout: 0, scriptPubKey: B.hex(spk), desc: 'addr(' + adresa + ')', amount: 0.001, height: 100 }] }, ['getrawtransaction:' + f.id]: verbose(f, blocSintetic('k4')) } };
|
|
})();
|
|
await incearca('i. Core cu --txids = finantarea unei adrese P2PKH a carei iesire e INCA necheltuita (scanul o gaseste): NEMASURAT numit ("cannot check that it lists every transaction"), nu F; PLANTAREA INVERSA (plasa de la capat scoasa) da F si iese ROSIE', async () => {
|
|
const r = await coreSintetic(AICI, 'k4', k4.raspunsuri, [k4.adresa], [k4.f.id]);
|
|
const bun = r.cod === 2 && eticheta(r.raport, k4.adresa) === U_ && nemasuratCu(r.raport, k4.adresa, NU_E_COMPLETA) && nemasuratCu(r.raport, k4.adresa, 'finds 1 of its output(s) unspent');
|
|
if (!bun) return [false, `unealta a dat ${detaliu(r.raport, k4.adresa).slice(0, 110)} (cod ${r.cod})`];
|
|
const d = copie('k4'); if (faraPlasaCore(d) !== 1) return 'tinta plantarii (plasa) nu apare o data';
|
|
const p = await coreSintetic(d, 'k4-inv', k4.raspunsuri, [k4.adresa], [k4.f.id]);
|
|
return [bun && eticheta(p.raport, k4.adresa) === F_ && !nemasuratCu(p.raport, k4.adresa, NU_E_COMPLETA), `${detaliu(r.raport, k4.adresa).slice(0, 100)} | copia plantata: ${eticheta(p.raport, k4.adresa)}`];
|
|
});
|
|
// j. EIP-7702 plantat pe contul fara iesiri
|
|
await incearca('j. un EOA cu designator EIP-7702 plantat (cod 0xef0100||adresa, nonce 1), fara autorizatia in fereastra: E, cu tranzactia de autorizare numita ca nevazuta', async () => {
|
|
const f = path.join(T, 'planta-7702.json');
|
|
fs.writeFileSync(f, JSON.stringify({ raspunsuri: stareEth([{ address: ETHV.primitor, nonce: 1, code: '0xef0100' + '11'.repeat(20) }]) }));
|
|
const m = await porneste('eth', path.join(FIX, 'eth-rpc.json'), ['--suprascrie', f]);
|
|
const r = ruleaza({ args: ['--mod', '2', '--adrese', scrieLista('unu-eth', [ETHV.primitor]), '--eth-rpc', m.url, '--eth-blocks', `${ETHV.bloc}..${ETHV.bloc}`], tinte: m.tinta }); m.opreste();
|
|
return [r.cod === 0 && eticheta(r.raport, ETHV.primitor) === E_ && dovada(r.raport, ETHV.primitor, 'EIP-7702 delegation designator') && nevazut(r.raport, ETHV.primitor, /authorizing transaction itself/), detaliu(r.raport, ETHV.primitor)];
|
|
});
|
|
// j. tranzactia 0x4 care poarta autorizatia: hash-ul ei se re-deriva din campurile servite, ca la o tranzactie trimisa. Ca sa se ajunga la
|
|
// ramura "numai autorizatia", expeditorul tranzactiei 0x4 e mutat pe alt cont (autorizatia se recupereaza din semnatura EI, nu din `from`)
|
|
const ethAut = (() => {
|
|
const fx = JSON.parse(fs.readFileSync(path.join(FIX, 'eth-rpc.json'), 'utf8')).raspunsuri; const blocKey = Object.keys(fx).find((k) => k.startsWith('eth_getBlockByNumber:'));
|
|
const autor = ETHV.autoritate7702.toLowerCase(); const codFixtura = fx['eth_getCode:' + JSON.stringify([autor, CAP_ETH])];
|
|
const fisier = (nume, { hash, cod }) => { const bloc = JSON.parse(JSON.stringify(fx[blocKey])); const t4 = bloc.transactions.find((t) => t.type === '0x4' && Array.isArray(t.authorizationList)); t4.from = '0x' + '11'.repeat(20); if (hash) t4.hash = hash; const r = { [blocKey]: bloc, ...stareEth([{ address: autor, nonce: 1, code: cod || codFixtura }]) }; const f = path.join(T, nume + '.json'); fs.writeFileSync(f, JSON.stringify({ raspunsuri: r })); return f; };
|
|
return { autor, golBun: fisier('eth-aut-gol-bun', { cod: '0x' }), golRau: fisier('eth-aut-gol-rau', { cod: '0x', hash: '0x' + 'ab'.repeat(32) }), delegatRau: fisier('eth-aut-delegat-rau', { hash: '0x' + 'ab'.repeat(32) }) };
|
|
})();
|
|
const ruleazaEthAut = async (dir, fx) => { const m = await porneste('eth', path.join(FIX, 'eth-rpc.json'), ['--suprascrie', fx]); const r = ruleaza({ dir, args: ['--mod', '2', '--adrese', scrieLista('eth-aut', [ethAut.autor]), '--eth-rpc', m.url, '--eth-blocks', `${ETHV.bloc}..${ETHV.bloc}`], tinte: m.tinta }); m.opreste(); return r; };
|
|
await incearca('j. autorizatia EIP-7702 a unui cont cu codul gol: tranzactia 0x4 servita exact -> E (hash-ul re-derivat se potriveste); cu hash-ul inlocuit (ab..ab) -> NEMASURAT ("do not hash to that hash"); cu designatorul in cod si hash-ul inlocuit -> E prin designator, autorizatia numita ca nefolosita, id-ul inventat nicaieri in dovezi', async () => {
|
|
const a = ethAut.autor; const r0 = await ruleazaEthAut(AICI, ethAut.golBun), r1 = await ruleazaEthAut(AICI, ethAut.golRau), r2 = await ruleazaEthAut(AICI, ethAut.delegatRau);
|
|
const inventat = 'ab'.repeat(32);
|
|
return [r0.cod === 0 && eticheta(r0.raport, a) === E_ && dovada(r0.raport, a, 'authorization signed by this account in transaction ' + ETHV.tx7702) && dovada(r0.raport, a, 'code at the head block is empty') && r1.cod === 2 && nemasuratCu(r1.raport, a, 'do not hash to that hash') && r2.cod === 0 && eticheta(r2.raport, a) === E_ && dovada(r2.raport, a, 'EIP-7702 delegation designator') && nevazut(r2.raport, a, /not used as evidence/) && !gaseste(r2.raport, a).evidence.some((e) => e.includes(inventat)), `exact: ${detaliu(r0.raport, a)} | hash inlocuit: ${detaliu(r1.raport, a).slice(0, 70)} | designator + hash inlocuit: ${detaliu(r2.raport, a)}`];
|
|
});
|
|
await incearca('j. PLANTAREA INVERSA: o copie care nu mai re-deriva hash-ul tranzactiei 0x4 (potrivire fortata) da E citand hash-ul inventat si face verificarea de mai sus ROSIE', async () => {
|
|
const d = copie('eth-aut'); const n = planteaza(path.join(d, 'adaptoare.mjs'), "txHashMatches: h !== null && String(t4.hash || '').toLowerCase() === h", 'txHashMatches: true');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const r = await ruleazaEthAut(d, ethAut.golRau);
|
|
return [!(r.cod === 2 && nemasuratCu(r.raport, ethAut.autor, 'do not hash to that hash')) && dovada(r.raport, ethAut.autor, 'ab'.repeat(32)), `copia plantata a dat ${detaliu(r.raport, ethAut.autor).slice(0, 80)} (cod ${r.cod})`];
|
|
});
|
|
// j. textul unui endpoint Ethereum care nu e o valoare (un hash, o cantitate, adresa delegatului) ajunge in raport numai citat sau
|
|
// refacut din octeti; o plantare per drum il lasa necitat, si poarta de limbaj refuza raportul
|
|
const ethServit = (() => {
|
|
const fx = JSON.parse(fs.readFileSync(path.join(FIX, 'eth-rpc.json'), 'utf8')).raspunsuri; const blocKey = Object.keys(fx).find((k) => k.startsWith('eth_getBlockByNumber:'));
|
|
const scrie = (nume, schimba) => { const r = {}; const bloc = JSON.parse(JSON.stringify(fx[blocKey])); schimba(bloc, r); r[blocKey] = bloc; const f = path.join(T, 'eth-servit-' + nume + '.json'); fs.writeFileSync(f, JSON.stringify({ raspunsuri: r })); return f; };
|
|
const exp = (b) => b.transactions.find((t) => typeof t.from === 'string' && t.from.toLowerCase() === ETHV.expeditor.toLowerCase());
|
|
const t4 = (b) => b.transactions.find((t) => t.type === '0x4' && Array.isArray(t.authorizationList));
|
|
const autor = ETHV.autoritate7702.toLowerCase();
|
|
const delegatReal = String(t4(JSON.parse(JSON.stringify(fx[blocKey]))).authorizationList[0].address).toLowerCase();
|
|
return [
|
|
{ nume: 'hash-ul tranzactiei trimise', adresa: ETHV.expeditor, fx: scrie('hash', (b) => { exp(b).hash = 'safe'; }), bun: (r) => r.cod === 2 && eticheta(r.raport, ETHV.expeditor) === U_ && nemasuratCu(r.raport, ETHV.expeditor, '[served: safe]'),
|
|
planta: [['adaptoare.mjs', 'obs.sentTx = { hash: idSauCitat(tx.hash, HASH_ETH), block: n', 'obs.sentTx = { hash: tx.hash, block: n']] },
|
|
{ nume: 'r-ul semnaturii (eroarea de conversie)', adresa: ETHV.expeditor, fx: scrie('r', (b) => { exp(b).r = 'safe'; }), bun: (r) => r.cod === 2 && eticheta(r.raport, ETHV.expeditor) === U_ && nemasuratCu(r.raport, ETHV.expeditor, 'r is not a JSON-RPC quantity'),
|
|
planta: [['ethereum.mjs', 'catch { throw new Error(`${nume} is not a JSON-RPC quantity`); }', 'catch (e) { throw e; }']] },
|
|
{ nume: 'eth_chainId', adresa: ETHV.expeditor, fx: scrie('chainid', (b, r) => { r['eth_chainId:[]'] = 'safe'; }), bun: (r) => r.cod === 2 && eticheta(r.raport, ETHV.expeditor) === U_ && nemasuratCu(r.raport, ETHV.expeditor, '[served: '),
|
|
planta: [['adaptoare.mjs', "const cantitate = (x) => (typeof x === 'string' && /^0x[0-9a-fA-F]{1,64}$/.test(x) ? BigInt(x) : null);", 'const cantitate = (x) => BigInt(x);']] },
|
|
{ nume: 'adresa delegatului cu text dupa cei 20 de octeti', adresa: autor, fx: scrie('delegat', (b, r) => { const t = t4(b); t.from = '0x' + '11'.repeat(20); t.authorizationList[0].address = t.authorizationList[0].address + ' safe'; Object.assign(r, stareEth([{ address: autor, nonce: 1, code: '0x' }])); }),
|
|
bun: (r) => r.cod === 0 && eticheta(r.raport, autor) === E_ && dovada(r.raport, autor, 'delegate ' + delegatReal + ')') && !JSON.stringify(r.raport).includes('safe'),
|
|
planta: [['ethereum.mjs', "delegate: '0x' + delegat.toString('hex') }", 'delegate: String(au.address).toLowerCase() }']] },
|
|
];
|
|
})();
|
|
for (const c of ethServit) {
|
|
await incearca(`j. textul endpoint-ului Ethereum: ${c.nume} cu "safe" -> raportul e scris si textul nu ajunge necitat in el; PLANTAREA INVERSA il lasa necitat, poarta de limbaj refuza raportul si verificarea iese ROSIE`, async () => {
|
|
const run = async (dir) => { const m = await porneste('eth', path.join(FIX, 'eth-rpc.json'), ['--suprascrie', c.fx]); const x = ruleaza({ dir, args: ['--mod', '2', '--adrese', scrieLista('eth-servit-' + c.nume.replace(/\W+/g, '-'), [c.adresa]), '--eth-rpc', m.url, '--eth-blocks', `${ETHV.bloc}..${ETHV.bloc}`], tinte: m.tinta }); m.opreste(); return x; };
|
|
const f = await run(AICI);
|
|
if (!c.bun(f)) return [false, `unealta a dat cod ${f.cod}: ${(f.raport ? detaliu(f.raport, c.adresa) : f.err).slice(0, 120)}`];
|
|
const d = copie('eth-servit-' + c.nume.replace(/\W+/g, '-').toLowerCase().slice(0, 20));
|
|
for (const [fis, vechi, nou] of c.planta) { const n = planteaza(path.join(d, fis), vechi, nou); if (n !== 1) return `tinta plantarii in ${fis} apare de ${n} ori`; }
|
|
const p = await run(d);
|
|
return [!c.bun(p) && p.cod === 1 && /REFUSED \(language\)/.test(p.err), `${detaliu(f.raport, c.adresa).slice(0, 90)} | copia plantata: cod ${p.cod} ${p.err.slice(0, 40)}`];
|
|
});
|
|
}
|
|
// ================================================================ k. runda 6 (revizia rundei 5, scratchpad c12rev6): varful tintei,
|
|
// scripturile-surori, mesajul TLS, cheltuirea din mempool numarata. Fiecare cu plantarea ei inversa (defectul pus inapoi intr-o copie)
|
|
const fisierSupra = (nume, raspunsuri) => { const f = path.join(T, nume + '.json'); fs.writeFileSync(f, JSON.stringify({ raspunsuri })); return f; };
|
|
// k.1 MEDIU-1: o tinta RAMASA IN URMA lantului. Varful vechi e antetul REAL al blocului 969686 (2026-10-03T06:29:12Z), primul antet din
|
|
// lantul de antete validat prin P2P de urmaritorul C6 pe 2026-10-11 (dovezi-l1/ots-noi/btc/antete.bin), acelasi lant din care vine varful
|
|
// fixturilor (971000); hash-ul lui il recalculeaza unealta din octeti
|
|
const VARF_VECHI = { height: 969686, hash: '00000000000000000001cb826a16ccfa8ab913f5346f8c387cfe65c16629fee3', hex: '004066219a8c00261cf0e47cb6e04c184202657330567bcdaed10100000000000000000020ff2db82f76d9fcea70a32806a47904803eb88ea51794caddc65feea6264383b8a0c06ac51e02178b4b1801' };
|
|
const varfSupra = (fel, v) => (fel === 'electrum' ? { 'blockchain.headers.subscribe': { height: v.height, hex: v.hex } } : { '/blocks/tip/height': String(v.height), ['/block-height/' + v.height]: v.hash, ['/block/' + v.hash + '/header']: v.hex });
|
|
const LISTA_NECH = scrieLista('k1-necheltuit', [A.p2pkh_necheltuit]);
|
|
const ruleazaCu = async (dir, fel, supra, lista) => { const m = await porneste(fel, path.join(FIX, fel + '.json'), supra ? ['--suprascrie', supra] : []); const r = ruleaza({ dir, args: ['--mod', '2', '--adrese', lista, fel === 'electrum' ? '--electrum' : '--esplora', fel === 'electrum' ? m.tinta : m.url], tinte: m.tinta }); m.opreste(); return r; };
|
|
const predicatVarf = (rProaspat, rVechi) => rProaspat.cod === 0 && eticheta(rProaspat.raport, A.p2pkh_necheltuit) === F_ && dovada(rProaspat.raport, A.p2pkh_necheltuit, 'history served up to height 971000 (tip header time 2026-10-11T22:53:11Z)') && rVechi.cod === 2 && nemasuratCu(rVechi.raport, A.p2pkh_necheltuit, 'target behind the chain: tip 969686 at 2026-10-03T06:29:12Z') && dovada(rVechi.raport, A.p2pkh_necheltuit, 'history served up to height 969686 (tip header time 2026-10-03T06:29:12Z)');
|
|
await incearca('k. MEDIU-1: adresa P2PKH reala niciodata cheltuita, cu varful inregistrat (971000, 2026-10-11T22:53:11Z, in marja de 2 ore fata de sfarsitul zilei raportului) -> F care numeste pana unde merge istoricul; cu o tinta RAMASA IN URMA (varful real 969686, 2026-10-03) -> NEMASURAT "target behind the chain: tip 969686 at ...", pe Electrum si pe Esplora; PLANTAREA INVERSA (varful nejudecat) da F si iese ROSIE', async () => {
|
|
const rez = [];
|
|
for (const fel of ['electrum', 'esplora']) { const supra = fisierSupra('k1-vechi-' + fel, varfSupra(fel, VARF_VECHI)); rez.push({ fel, supra, r1: await ruleazaCu(AICI, fel, null, LISTA_NECH), r2: await ruleazaCu(AICI, fel, supra, LISTA_NECH) }); }
|
|
const det = rez.map((x) => `${x.fel}: ${detaliu(x.r1.raport, A.p2pkh_necheltuit).slice(0, 35)} / ${detaliu(x.r2.raport, A.p2pkh_necheltuit).slice(13, 80)}`).join(' | ');
|
|
if (!rez.every((x) => predicatVarf(x.r1, x.r2))) return [false, det];
|
|
const d = copie('k1-varf'); const n = planteaza(path.join(d, 'clasifica.mjs'), 'else if (obs.tip.time * 1000 < ctx.referinta - MARJA_VARF_ORE * 3600 * 1000) r = nem(', 'else if (false) r = nem(');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const p = await ruleazaCu(d, 'electrum', rez[0].supra, LISTA_NECH);
|
|
return [!predicatVarf(rez[0].r1, p) && eticheta(p.raport, A.p2pkh_necheltuit) === F_, `${det} | copia cu varful nejudecat: ${detaliu(p.raport, A.p2pkh_necheltuit).slice(0, 40)}`];
|
|
});
|
|
await incearca('k. MEDIU-1: un antet de varf cu UN octet schimbat (ora) nu mai e un antet (nu atinge dovada de lucru din campul lui bits) -> NEMASURAT; Esplora: un antet care nu se hashuieste la hash-ul pe care indexul il da pentru varf -> NEMASURAT; aici antetul e judecat numai fata de propriul lui camp bits (lantul caruia ii poate apartine, prin geneza si limita de dovada de lucru a acestuia, se judeca in l. MEDIU-A)', async () => {
|
|
const elV = JSON.parse(fs.readFileSync(path.join(FIX, 'electrum.json'), 'utf8')).raspunsuri['blockchain.headers.subscribe'];
|
|
const rau = Buffer.from(elV.hex, 'hex'); rau[68] ^= 1;
|
|
const r1 = await ruleazaCu(AICI, 'electrum', fisierSupra('k1-antet-rau', { 'blockchain.headers.subscribe': { height: elV.height, hex: rau.toString('hex') } }), LISTA_NECH);
|
|
const hashVarf = JSON.parse(fs.readFileSync(path.join(FIX, 'esplora.json'), 'utf8')).raspunsuri['/block-height/971000'];
|
|
const r2 = await ruleazaCu(AICI, 'esplora', fisierSupra('k1-antet-strain', { ['/block/' + hashVarf + '/header']: VARF_VECHI.hex }), LISTA_NECH);
|
|
return [r1.cod === 2 && nemasuratCu(r1.raport, A.p2pkh_necheltuit, 'is not a block header') && nemasuratCu(r1.raport, A.p2pkh_necheltuit, 'proof of work') && r2.cod === 2 && nemasuratCu(r2.raport, A.p2pkh_necheltuit, 'hashes to ' + VARF_VECHI.hash), `electrum: ${detaliu(r1.raport, A.p2pkh_necheltuit).slice(13, 110)} | esplora: ${detaliu(r2.raport, A.p2pkh_necheltuit).slice(13, 90)}`];
|
|
});
|
|
// k.2 MEDIU-2: aceeasi cheie in spatele mai multor tipuri de script. Cheia sintetica e dezvaluita NUMAI de cheltuirea adresei P2WPKH a
|
|
// ei; se cer adresa P2PKH a aceluiasi HASH160 si linia p2pk: a cheii. Surorile se citesc prin aceeasi tinta (Electrum: scripthash;
|
|
// Esplora: /scripthash/, fiindca unealta citeste o sora dupa script, nu dupa numele ei); prin Core, pe tranzactiile date
|
|
const sora = (() => {
|
|
const k = cheie('k2-sora'); const h = B.hash160(k.pub); const spkW = Buffer.concat([Buffer.from([0x00, 0x14]), h]);
|
|
const adresaW = B.segwitEncode('bc', 0, h), adresaP = b58check(0, h), intrareK = 'p2pk:' + B.hex(k.pub);
|
|
const f = finantare(spkW, 'k2-sora'); const s = cheltuire(f.id, Buffer.alloc(0), [SIG, k.pub]);
|
|
const shEs = B.hex(B.sha256(spkW));
|
|
const stats = { scripthash: shEs, chain_stats: { funded_txo_count: 1, funded_txo_sum: 100000, spent_txo_count: 1, spent_txo_sum: 100000, tx_count: 2 }, mempool_stats: { funded_txo_count: 0, funded_txo_sum: 0, spent_txo_count: 0, spent_txo_sum: 0, tx_count: 0 } };
|
|
return { adresaW, adresaP, intrareK, numeK: 'script:' + B.hex(spkW), f, s, lista: scrieLista('k2-sora', [adresaP, intrareK]), el: suprascriereElectrum('k2-sora-el', spkW, [{ ...f, height: 1 }, { ...s, height: 2 }]),
|
|
es: fisierSupra('k2-sora-es', { ['/scripthash/' + shEs]: stats, ['/scripthash/' + shEs + '/txs']: [{ ...s, height: 2 }, { ...f, height: 1 }].map(esploraTx) }) };
|
|
})();
|
|
const predicatSora = (r) => !!r.raport && [[sora.adresaP, sora.adresaW], [sora.intrareK, sora.numeK]].every(([a, numeSora]) => eticheta(r.raport, a) === E_ && dovada(r.raport, a, 'the same HASH160 of the key under another script type, P2WPKH ' + numeSora + ':') && dovada(r.raport, a, 'public key revealed in the witness'));
|
|
await incearca('k. MEDIU-2: cheia dezvaluita NUMAI la adresa-sora P2WPKH: adresa P2PKH a aceluiasi HASH160 si linia p2pk: a cheii ies E cu sora numita, prin Electrum, Esplora si Bitcoin Core (pe tranzactiile date); PLANTAREA INVERSA (surorile neinterogate) da F si iese ROSIE', async () => {
|
|
const r1 = await ruleazaCu(AICI, 'electrum', sora.el, sora.lista), r2 = await ruleazaCu(AICI, 'esplora', sora.es, sora.lista);
|
|
const r3 = await coreSintetic(AICI, 'k2-sora', { ['getrawtransaction:' + sora.f.id]: verbose(sora.f, blocSintetic('k2-f')), ['getrawtransaction:' + sora.s.id]: verbose(sora.s, blocSintetic('k2-s')) }, [sora.adresaP, sora.intrareK], [sora.f.id, sora.s.id]);
|
|
const det = [r1, r2, r3].map((r, i) => `${['electrum', 'esplora', 'core'][i]}: ${detaliu(r.raport, sora.adresaP).slice(0, 30)} / ${detaliu(r.raport, sora.intrareK).slice(0, 30)}`).join(' | ');
|
|
if (!(r1.cod === 0 && r2.cod === 0 && predicatSora(r1) && predicatSora(r2) && predicatSora(r3))) return [false, det];
|
|
const d = copie('k2-surori'); const n = planteaza(path.join(d, 'bitcoin.mjs'), " if (t.type === 'p2pkh') h = t.hash; else if (t.type === 'p2wpkh') h = t.program; else if (t.type === 'p2pk') h = hash160(t.key); else return [];", ' return [];');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const p = await ruleazaCu(d, 'electrum', sora.el, sora.lista);
|
|
return [!predicatSora(p) && eticheta(p.raport, sora.adresaP) === F_ && eticheta(p.raport, sora.intrareK) === F_, `${det} | copia fara surori: ${detaliu(p.raport, sora.adresaP).slice(0, 34)} / ${detaliu(p.raport, sora.intrareK).slice(0, 34)}`];
|
|
});
|
|
await incearca('k. MEDIU-2: o adresa-sora care NU se poate masura (eroare servita la istoricul ei) lasa adresa P2PKH (numai finantata, altfel F) NEMASURAT, cu sora numita; fara eroare -> F cu surorile citite numite (controlul pozitiv); PLANTAREA INVERSA (sora nemasurata ignorata) da F si iese ROSIE', async () => {
|
|
const k = cheie('k2-sora-rea'); const h = B.hash160(k.pub); const spkP = p2pkhSpk(h), spkW = Buffer.concat([Buffer.from([0x00, 0x14]), h]); const adresaP = b58check(0, h), adresaW = B.segwitEncode('bc', 0, h);
|
|
const fin = finantare(spkP, 'k2-sora-rea'); const lista = scrieLista('k2-sora-rea', [adresaP]);
|
|
const bun = suprascriereElectrum('k2-sora-buna', spkP, [{ ...fin, height: 1 }]);
|
|
const rea = fisierSupra('k2-sora-rea', { ...JSON.parse(fs.readFileSync(bun, 'utf8')).raspunsuri, ['blockchain.scripthash.get_history:' + B.scripthash(spkW)]: { _eroare: { code: -32000, message: 'mock: sibling history not served' } } });
|
|
const predicat = (r) => r.cod === 2 && nemasuratCu(r.raport, adresaP, 'the sibling script P2WPKH ' + adresaW) && nemasuratCu(r.raport, adresaP, '[served: -32000 mock: sibling history not served]');
|
|
const r1 = await ruleazaCu(AICI, 'electrum', rea, lista), r0 = await ruleazaCu(AICI, 'electrum', bun, lista);
|
|
const det = `sora rea: ${detaliu(r1.raport, adresaP).slice(13, 110)} | sora buna: ${detaliu(r0.raport, adresaP)}`;
|
|
if (!(predicat(r1) && r0.cod === 0 && eticheta(r0.raport, adresaP) === F_ && dovada(r0.raport, adresaP, 'sibling scripts of the same HASH160 read through the same target: P2WPKH ' + adresaW))) return [false, det];
|
|
const d = copie('k2-sora-rea'); const n = planteaza(path.join(d, 'clasifica.mjs'), 'if (nem_) r = nem(', 'if (false) r = nem(');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const p = await ruleazaCu(d, 'electrum', rea, lista);
|
|
return [!predicat(p) && eticheta(p.raport, adresaP) === F_, `${det} | copia care ignora sora nemasurata: ${detaliu(p.raport, adresaP)}`];
|
|
});
|
|
// k.3 MIC-2: textul unei erori TLS e al TINTEI (ERR_TLS_CERT_ALTNAME_INVALID numeste toate numele din certificatul ei). Un ascultator TLS
|
|
// al probei, pe 127.0.0.1, cu un certificat facut AICI (cheie EC noua la fiecare rulare, nimic in arbore) al carui SAN e un nume cu un
|
|
// cuvant interzis; certificatul e de incredere numai pentru procesul raportului (NODE_EXTRA_CA_CERTS), deci eroarea e cea a numelui
|
|
const der = (tag, ...p) => { const b = Buffer.concat(p); const n = b.length; return Buffer.concat([Buffer.from([tag]), n < 128 ? Buffer.from([n]) : n < 256 ? Buffer.from([0x81, n]) : Buffer.from([0x82, n >> 8, n & 255]), b]); };
|
|
const oid = (s) => { const a = s.split('.').map(Number); const o = [40 * a[0] + a[1]]; for (const x of a.slice(2)) { const t = []; let v = x; do { t.unshift((v & 127) | (t.length ? 128 : 0)); v = Math.floor(v / 128); } while (v); o.push(...t); } return der(0x06, Buffer.from(o)); };
|
|
const certificatProba = (san) => {
|
|
const { publicKey, privateKey } = crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
|
|
const seq = (...p) => der(0x30, ...p); const alg = seq(oid('1.2.840.10045.4.3.2'));
|
|
const nume = seq(der(0x31, seq(oid('2.5.4.3'), der(0x0c, Buffer.from('aere-proba-expunere')))));
|
|
const ext = der(0xa3, seq(seq(oid('2.5.29.19'), der(0x01, Buffer.from([0xff])), der(0x04, seq(der(0x01, Buffer.from([0xff]))))), seq(oid('2.5.29.17'), der(0x04, seq(der(0x82, Buffer.from(san)))))));
|
|
const serie = crypto.randomBytes(8); serie[0] = 0x11;
|
|
const tbs = seq(der(0xa0, der(0x02, Buffer.from([2]))), der(0x02, serie), alg, nume, seq(der(0x17, Buffer.from('260101000000Z')), der(0x17, Buffer.from('361231235959Z'))), nume, publicKey.export({ type: 'spki', format: 'der' }), ext);
|
|
const cert = seq(tbs, alg, der(0x03, Buffer.concat([Buffer.from([0]), crypto.sign('sha256', tbs, privateKey)])));
|
|
return { pem: '-----BEGIN CERTIFICATE-----\n' + cert.toString('base64').match(/.{1,64}/g).join('\n') + '\n-----END CERTIFICATE-----\n', cheie: privateKey.export({ type: 'pkcs8', format: 'pem' }) };
|
|
};
|
|
await incearca('k. MIC-2: o tinta TLS (Electrum tls://, Esplora https://) al carei certificat numeste "secure.example": raportul e SCRIS (cod 2), adresa NEMASURAT cu mesajul TLS citat intreg [served: ...] (numele din certificat inauntru); PLANTAREA INVERSA (mesajul TLS necitat) e refuzata de poarta de limbaj si iese ROSIE', async () => {
|
|
const c = certificatProba('secure.example');
|
|
if (!new crypto.X509Certificate(c.pem).subjectAltName.includes('secure.example')) return 'certificatul probei nu poarta SAN-ul cerut';
|
|
const fCert = path.join(T, 'k3-cert.pem'); fs.writeFileSync(fCert, c.pem);
|
|
const tls = await import('node:tls'); let acceptate = 0;
|
|
const srv = tls.createServer({ cert: c.pem, key: c.cheie }, (s) => { s.on('error', () => {}); s.end(); });
|
|
srv.on('connection', () => { acceptate++; }); srv.on('tlsClientError', () => {});
|
|
await new Promise((res) => srv.listen(0, '127.0.0.1', res)); const port = srv.address().port;
|
|
const lista = scrieLista('k3-tls', [A.p2pkh_necheltuit]);
|
|
const env = { NODE_EXTRA_CA_CERTS: fCert, AERE_EXPUNERE_TIMEOUT_MS: '5000' };
|
|
const caz = (dir, fel) => ruleazaAsincron({ dir, args: ['--mod', '2', '--adrese', lista, fel === 'electrum' ? '--electrum' : '--esplora', (fel === 'electrum' ? 'tls://localhost:' : 'https://localhost:') + port], tinte: 'localhost:' + port, env });
|
|
const bun = (r) => r.cod === 2 && r.scris && nemasuratCu(r.raport, A.p2pkh_necheltuit, 'ERR_TLS_CERT_ALTNAME_INVALID [served: ') && nemasuratCu(r.raport, A.p2pkh_necheltuit, 'secure.example');
|
|
try {
|
|
const r1 = await caz(AICI, 'electrum'), r2 = await caz(AICI, 'esplora');
|
|
const det = `electrum: cod ${r1.cod} ${(r1.raport ? detaliu(r1.raport, A.p2pkh_necheltuit) : r1.err).slice(13, 110)} | esplora: cod ${r2.cod}`;
|
|
if (!(bun(r1) && bun(r2) && acceptate >= 2)) return [false, det + ` | conexiuni acceptate ${acceptate}`];
|
|
const d = copie('k3-tls'); const n = planteaza(path.join(d, 'adaptoare.mjs'), "(eroareTls(e) ? e.code + ' ' + servit(e.message) :", "(false ? '' :");
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const p = await caz(d, 'electrum');
|
|
return [!bun(p) && p.cod === 1 && /REFUSED \(language\)/.test(p.err), `${det} | copia cu mesajul necitat: cod ${p.cod} ${p.err.slice(0, 40)}`];
|
|
} finally { srv.close(); }
|
|
});
|
|
// k.4 MIC-3: indexul numara in mempool o cheltuire a adresei pe care pagina NU o poarta, dar pagina poarta ALTA tranzactie din mempool a
|
|
// adresei (finantarea). Regula: numaratoarea se compara cu intrarile neconfirmate servite care CHELTUIE adresa (aici 0)
|
|
const mpl = (() => {
|
|
const k = cheie('k4-mempool'); const h = B.hash160(k.pub); const adresa = b58check(0, h); const f = finantare(p2pkhSpk(h), 'k4-mempool');
|
|
const stats = (n) => ({ address: adresa, chain_stats: { funded_txo_count: 0, funded_txo_sum: 0, spent_txo_count: 0, spent_txo_sum: 0, tx_count: 0 }, mempool_stats: { funded_txo_count: 1, funded_txo_sum: 100000, spent_txo_count: n, spent_txo_sum: 0, tx_count: 1 + n } });
|
|
const fx = (nume, n) => fisierSupra(nume, { ['/address/' + adresa]: stats(n), ['/address/' + adresa + '/txs']: [esploraTx({ ...f, height: 0 })] });
|
|
return { adresa, lista: scrieLista('k4-mempool', [adresa]), a: fx('k4-a', 1), zero: fx('k4-zero', 0) };
|
|
})();
|
|
const predicatMempool = (r) => r.cod === 2 && nemasuratCu(r.raport, mpl.adresa, 'mempool_stats') && nemasuratCu(r.raport, mpl.adresa, 'carried 0 unconfirmed spend(s)');
|
|
await incearca('k. MIC-3: Esplora numara in mempool o cheltuire a adresei pe care pagina nu o poarta, langa ALTA tranzactie din mempool a adresei pe care o poarta (finantarea) -> NEMASURAT ("mempool_stats ... carried 0 unconfirmed spend(s)"); aceeasi pagina cu numaratoarea 0 -> F (controlul pozitiv); PLANTAREA INVERSA (regula veche: numai o pagina fara mempool) da F si iese ROSIE', async () => {
|
|
const r1 = await ruleazaCu(AICI, 'esplora', mpl.a, mpl.lista), r0 = await ruleazaCu(AICI, 'esplora', mpl.zero, mpl.lista);
|
|
const det = `numarata 1: ${detaliu(r1.raport, mpl.adresa).slice(13, 120)} | numarata 0: ${detaliu(r0.raport, mpl.adresa)}`;
|
|
if (!(predicatMempool(r1) && r0.cod === 0 && eticheta(r0.raport, mpl.adresa) === F_)) return [false, det];
|
|
const d = copie('k4-mempool'); const n = planteaza(path.join(d, 'adaptoare.mjs'), 'obs.mempoolSpendNotServed = obs.mempoolSpendsCounted > obs.unconfirmedSpends;', 'obs.mempoolSpendNotServed = obs.mempoolSpendsCounted > 0 && neconf.length === 0;');
|
|
// din runda 7 tine si plasa tranzactiilor numarate in mempool (tx_count 2, pagina poarta 1): plantarea o scoate si pe ea, ca regula
|
|
// cheltuirilor sa fie aratata singura
|
|
const n2 = planteaza(path.join(d, 'clasifica.mjs'), PLASA_TX_MEMPOOL, 'if (false) return nem(');
|
|
if (n !== 1 || n2 !== 1) return `tinta plantarii apare de ${n} / ${n2} ori`;
|
|
const p = await ruleazaCu(d, 'esplora', mpl.a, mpl.lista);
|
|
return [!predicatMempool(p) && eticheta(p.raport, mpl.adresa) === F_, `${det} | copia cu regula veche: ${detaliu(p.raport, mpl.adresa).slice(0, 40)}`];
|
|
});
|
|
|
|
// ================================================================ l. runda 7 (revizia rundei 6, scratchpad c12rev7): lantul pe care il
|
|
// serveste tinta (MEDIU-A), capul unui endpoint Ethereum (MEDIU-B), data raportului (MEDIU-C), surorile unei linii p2pk: (MIC-D),
|
|
// numaratorile din mempool ale unui index (MIC-E). Fiecare cu plantarea ei inversa (defectul pus inapoi intr-o copie)
|
|
// tintele plantarilor: sirurile EXACTE din unealta (o plantare care nu-si gaseste tinta e STRICAT)
|
|
const PLANTA_GENEZA = 'if (!R || !permise.includes(retea)) return';
|
|
const PLANTA_LIMITA = 'if (obs.tip && (tintaVarf === null || tintaVarf > R.limita)) return {';
|
|
const PLANTA_CAP_ETH = 'else if (obs.head.time * 1000 < ctx.referinta - MARJA_CAP_ETH_MIN * 60 * 1000) r = nem(';
|
|
const PLANTA_SYNC_ETH = 'if (obs.syncing) r = nem(';
|
|
const PLANTA_DATA = 'if (timp * 1000 > ctx.referinta + MARJA_VARF_ORE * 3600 * 1000) return';
|
|
const PLANTA_BLOCKTIME = 'if (obs.blocktimeLipsa) return numaiTinta(';
|
|
const PLANTA_IESIRI_MEMPOOL = 'if (tipExpus && !outs.length && obs.mempoolOutputNotServed) return nem(';
|
|
// o ora PROASPATA fata de referinta fixa a probei (sfarsitul zilei DATA, 2026-10-12T00:00:00Z): 30 de minute inainte, in marja de 2 ore
|
|
const ORA_PROASPATA = Date.parse('2026-10-11T23:30:00Z') / 1000;
|
|
// un antet care atinge dovada de lucru a PROPRIULUI camp bits; prev / merkle din etichete AERE-SINTETIC (bits regtest: ~2 incercari)
|
|
const mineaza = (bits, timp, eticheta, inaltime) => {
|
|
const b = Buffer.alloc(80); b.writeInt32LE(0x20000000, 0); crypto.createHash('sha256').update('AERE-SINTETIC prev ' + eticheta).digest().copy(b, 4); crypto.createHash('sha256').update('AERE-SINTETIC merkle ' + eticheta).digest().copy(b, 36);
|
|
b.writeUInt32LE(timp >>> 0, 68); b.writeUInt32LE(bits >>> 0, 72); const T_ = B.tintaDinBits(bits);
|
|
for (let nonce = 0; nonce < 1e7; nonce++) { b.writeUInt32LE(nonce, 76); const h = B.hex(Buffer.from(B.sha256d(b)).reverse()); if (BigInt('0x' + h) <= T_) return { height: inaltime, hash: h, hex: b.toString('hex') }; }
|
|
throw new Error('nu s-a gasit un nonce pentru ' + eticheta);
|
|
};
|
|
const VARF_REGTEST = mineaza(0x207fffff, ORA_PROASPATA, 'l-regtest', 300);
|
|
// antetul signet (bits 0x1e0377ae, dificultatea de pornire a signet-ului) e minat O DATA, nu la fiecare rulare (cateva milioane de
|
|
// incercari): scratchpad c12r8/mineaza-signet.mjs, ora 2026-10-11T23:30:00Z, prev / merkle din etichete AERE-SINTETIC c12r8. Unealta
|
|
// ii recalculeaza hash-ul si dovada de lucru din octeti; proba cere aici ca el sa fie chiar un antet al campului lui bits
|
|
const VARF_SIGNET = { height: 270000, hash: '000002fade4eff66ab627804c0864252d516aa7e125693450f49099498e9ed40', hex: '0000002095869373f6ad2d63ae26780334b4915914b365bd39306ae08bb035fd82b38cc839cbfd5104e99020ff8c55820b09dc2b99ade9450e483e54b23efdb4e0ab352ff81bcc6aae77031ee8d20900' };
|
|
/** suprascrierea tintei: geneza (null = cea a fixturii, mainnet) si varful */
|
|
const supraLant = (fel, geneza, v) => (fel === 'electrum'
|
|
? { ...(geneza ? { 'server.features': { genesis_hash: geneza, hash_function: 'sha256', protocol_max: '1.4', protocol_min: '1.4' } } : {}), 'blockchain.headers.subscribe': { height: v.height, hex: v.hex } }
|
|
: { ...(geneza ? { '/block-height/0': geneza } : {}), '/blocks/tip/height': String(v.height), ['/block-height/' + v.height]: v.hash, ['/block/' + v.hash + '/header']: v.hex });
|
|
// l.1 MEDIU-A: o tinta pe ALT LANT (regtest: geneza ei, un varf regtest proaspat). O adresa mainnet nu primeste nicio eticheta de
|
|
// acolo; o adresa regtest (bcrt1) prin aceeasi tinta e masurata (controlul pozitiv: garda nu inseamna "numai mainnet")
|
|
const ADR_REGTEST = B.segwitEncode('bcrt', 0, B.hash160(cheie('l1-regtest').pub));
|
|
const LISTA_L1 = scrieLista('l1-lant', [A.p2pkh_necheltuit, ADR_REGTEST]);
|
|
const predicatGeneza = (r) => r.cod === 2 && nemasuratCu(r.raport, A.p2pkh_necheltuit, 'the target serves another chain: Bitcoin regtest, and this is a mainnet address') && eticheta(r.raport, ADR_REGTEST) === F_ && dovada(r.raport, ADR_REGTEST, 'chain served: Bitcoin regtest (genesis block ' + B.RETELE_BTC.regtest.geneza + '; the tip header states a target within its proof-of-work limit; no checkpoint is held for Bitcoin regtest');
|
|
await incearca('l. MEDIU-A: o tinta care serveste ALT LANT (geneza regtest, varf regtest minat aici cu ora proaspata): adresa P2PKH mainnet iese NEMASURAT "the target serves another chain: Bitcoin regtest", iar o adresa regtest (bcrt1) prin aceeasi tinta e masurata (F cu lantul numit: controlul pozitiv), pe Electrum si pe Esplora; PLANTAREA INVERSA (geneza necomparata cu reteaua adresei) da F pe adresa mainnet si iese ROSIE', async () => {
|
|
const rez = []; for (const fel of ['electrum', 'esplora']) rez.push(await ruleazaCu(AICI, fel, fisierSupra('l1-' + fel, supraLant(fel, B.RETELE_BTC.regtest.geneza, VARF_REGTEST)), LISTA_L1));
|
|
const det = rez.map((r, i) => `${['electrum', 'esplora'][i]}: ${detaliu(r.raport, A.p2pkh_necheltuit).slice(13, 95)} / bcrt1: ${eticheta(r.raport, ADR_REGTEST)}`).join(' | ');
|
|
if (!rez.every(predicatGeneza)) return [false, det];
|
|
const d = copie('l1-geneza'); const n = planteaza(path.join(d, 'clasifica.mjs'), PLANTA_GENEZA, 'if (!R) return');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const p = await ruleazaCu(d, 'electrum', fisierSupra('l1-electrum-inv', supraLant('electrum', B.RETELE_BTC.regtest.geneza, VARF_REGTEST)), LISTA_L1);
|
|
return [!predicatGeneza(p) && eticheta(p.raport, A.p2pkh_necheltuit) === F_, `${det} | copia fara comparatia genezei: ${detaliu(p.raport, A.p2pkh_necheltuit).slice(0, 40)}`];
|
|
});
|
|
// l.2 MEDIU-A: un varf SIGNET (bits 0x1e0377ae, minat) sub geneza mainnet a fixturii: niciun antet mainnet nu poate purta acel bits
|
|
// (limita de dovada de lucru a mainnet-ului e 0x00000000ff..ff). Cu geneza signet, acelasi varf e legitim pentru o adresa tb1, iar o
|
|
// linie p2pk: (fara retea) se citeste numai pe mainnet
|
|
const ADR_TB = B.segwitEncode('tb', 0, B.hash160(cheie('l2-signet').pub));
|
|
const LISTA_L2 = scrieLista('l2-mainnet', [A.p2pkh_necheltuit, A.p2pk]), LISTA_L2_TB = scrieLista('l2-signet', [ADR_TB, A.p2pk]);
|
|
const predicatLimita = (r) => r.cod === 2 && [A.p2pkh_necheltuit, A.p2pk].every((a) => nemasuratCu(r.raport, a, 'the target serves another chain: the tip header it serves (height 270000, bits 0x1e0377ae) states a proof-of-work target above the limit of Bitcoin mainnet'));
|
|
const predicatSignet = (r) => eticheta(r.raport, ADR_TB) === F_ && dovada(r.raport, ADR_TB, 'chain served: Bitcoin signet (genesis block ' + B.RETELE_BTC.signet.geneza + '; the tip header states a target within its proof-of-work limit; no checkpoint is held for Bitcoin signet') && nemasuratCu(r.raport, A.p2pk, 'the target serves another chain: Bitcoin signet, and this is a p2pk: line, which this tool reads on Bitcoin mainnet only');
|
|
await incearca('l. MEDIU-A: varf SIGNET (minat, bits 0x1e0377ae, ora proaspata) sub geneza MAINNET: adresa P2PKH si linia p2pk: ies NEMASURAT ("above the limit of Bitcoin mainnet"), pe Electrum si pe Esplora; acelasi varf sub geneza signet: adresa tb1 e masurata (F, controlul pozitiv), linia p2pk: NEMASURAT ("on Bitcoin mainnet only"); PLANTAREA INVERSA (limita de dovada de lucru nejudecata) da F pe adresa mainnet si iese ROSIE', async () => {
|
|
const a = B.antetBloc(VARF_SIGNET.hex); if (a.eroare || a.hash !== VARF_SIGNET.hash || a.bits !== 0x1e0377ae || a.time !== ORA_PROASPATA) return `antetul signet al probei nu e ce spune (${a.eroare || a.hash.slice(0, 16)})`;
|
|
const rez = []; for (const fel of ['electrum', 'esplora']) rez.push(await ruleazaCu(AICI, fel, fisierSupra('l2-' + fel, supraLant(fel, null, VARF_SIGNET)), LISTA_L2));
|
|
const rTb = await ruleazaCu(AICI, 'electrum', fisierSupra('l2-tb', supraLant('electrum', B.RETELE_BTC.signet.geneza, VARF_SIGNET)), LISTA_L2_TB);
|
|
const det = rez.map((r, i) => `${['electrum', 'esplora'][i]}: ${detaliu(r.raport, A.p2pkh_necheltuit).slice(13, 70)}..`).join(' | ') + ` | geneza signet: tb1 ${eticheta(rTb.raport, ADR_TB)}, p2pk: ${detaliu(rTb.raport, A.p2pk).slice(13, 80)}`;
|
|
if (!(rez.every(predicatLimita) && predicatSignet(rTb))) return [false, det];
|
|
// din runda 8 tine si regula punctului de control (varful signet, la 270000, e sub cel mai jos punct, 967680): plantarea o scoate si pe
|
|
// ea, ca limita de dovada de lucru sa fie aratata singura
|
|
const d = copie('l2-limita'); const n = planteaza(path.join(d, 'clasifica.mjs'), PLANTA_LIMITA, 'if (false) return {'), n2 = planteaza(path.join(d, 'clasifica.mjs'), 'if (c.sub !== undefined) return {', 'if (false) return {');
|
|
if (n !== 1 || n2 !== 1) return `tinta plantarii apare de ${n} / ${n2} ori`;
|
|
const p = await ruleazaCu(d, 'electrum', fisierSupra('l2-electrum-inv', supraLant('electrum', null, VARF_SIGNET)), LISTA_L2);
|
|
return [!predicatLimita(p) && eticheta(p.raport, A.p2pkh_necheltuit) === F_, `${det} | copia fara limita: ${detaliu(p.raport, A.p2pkh_necheltuit).slice(0, 40)}`];
|
|
});
|
|
// l.3 MEDIU-B: capul unui endpoint Ethereum. Ramas in urma (capul la blocul 18.000.000, 2023-09-07; nonce 0 acolo) sau care spune ca se
|
|
// sincronizeaza (eth_syncing) -> NEMASURAT "endpoint behind the chain"; cu capul fixturii (2026-10-11T23:45:11Z, cea mai devreme ora pe
|
|
// care o poate purta blocul 26172585: _sursa.nota) -> F care numeste capul. Doua plantari inverse, fiecare pe garda ei
|
|
const CAP_VECHI = '0x112a880';
|
|
const supraCapVechi = fisierSupra('l3-cap-vechi', { 'eth_blockNumber:[]': CAP_VECHI, ...stareEth([{ address: ETHV.primitor, nonce: 0, code: '0x' }], CAP_VECHI, '0x64fa4a17'), [`eth_getBlockByNumber:${JSON.stringify([CAP_VECHI, true])}`]: { number: CAP_VECHI, timestamp: '0x64fa4a17', hash: '0x' + '11'.repeat(32), transactions: [] } });
|
|
const supraSync = fisierSupra('l3-sync', { 'eth_syncing:[]': { startingBlock: '0x0', currentBlock: '0x18f5ca9', highestBlock: '0x18f6000' } });
|
|
const ruleazaEth = async (dir, supra, fereastra, extra = {}) => { const m = await porneste('eth', path.join(FIX, 'eth-rpc.json'), supra ? ['--suprascrie', supra] : []); const r = ruleaza({ dir, args: ['--mod', '2', '--adrese', scrieLista('l3-eth', [ETHV.primitor]), '--eth-rpc', m.url, '--eth-blocks', fereastra], tinte: m.tinta, ...extra }); m.opreste(); return r; };
|
|
const FER = `${ETHV.bloc}..${ETHV.bloc}`;
|
|
const predicatCapVechi = (r) => r.cod === 2 && nemasuratCu(r.raport, ETHV.primitor, 'endpoint behind the chain: head block 18000000 at 2023-09-07T22:09:27Z, more than 30 minutes before');
|
|
const predicatSync = (r) => r.cod === 2 && nemasuratCu(r.raport, ETHV.primitor, 'endpoint behind the chain: it reports that it is syncing (eth_syncing: current block 26172585, highest block 26173440');
|
|
await incearca('l. MEDIU-B: un endpoint Ethereum RAMAS IN URMA (capul la blocul 18.000.000, 2023-09-07, nonce 0 acolo) sau care raporteaza ca se sincronizeaza (eth_syncing) -> NEMASURAT "endpoint behind the chain", nu F pe nonce 0; cu capul fixturii -> F care numeste capul si ora lui; PLANTARILE INVERSE (ora capului nejudecata; eth_syncing ignorat) dau F si ies ROSII', async () => {
|
|
const r0 = await ruleazaEth(AICI, null, FER), r1 = await ruleazaEth(AICI, supraCapVechi, '1'), r2 = await ruleazaEth(AICI, supraSync, FER);
|
|
const det = `capul fixturii: ${detaliu(r0.raport, ETHV.primitor)} | cap vechi: ${detaliu(r1.raport, ETHV.primitor).slice(13, 100)} | sync: ${detaliu(r2.raport, ETHV.primitor).slice(13, 90)}`;
|
|
if (!(r0.cod === 0 && eticheta(r0.raport, ETHV.primitor) === F_ && dovada(r0.raport, ETHV.primitor, 'head block 26172585 served by the endpoint (block time 2026-10-11T23:45:11Z)') && predicatCapVechi(r1) && predicatSync(r2))) return [false, det];
|
|
const d1 = copie('l3-cap'), d2 = copie('l3-sync');
|
|
const n1 = planteaza(path.join(d1, 'clasifica.mjs'), PLANTA_CAP_ETH, 'else if (false) r = nem('), n2 = planteaza(path.join(d2, 'clasifica.mjs'), PLANTA_SYNC_ETH, 'if (false) r = nem(');
|
|
if (n1 !== 1 || n2 !== 1) return `tinta plantarii apare de ${n1} / ${n2} ori`;
|
|
const p1 = await ruleazaEth(d1, supraCapVechi, '1'), p2 = await ruleazaEth(d2, supraSync, FER);
|
|
return [!predicatCapVechi(p1) && eticheta(p1.raport, ETHV.primitor) === F_ && !predicatSync(p2) && eticheta(p2.raport, ETHV.primitor) === F_, `${det} | copia cu ora capului nejudecata: ${eticheta(p1.raport, ETHV.primitor)}; copia fara eth_syncing: ${eticheta(p2.raport, ETHV.primitor)}`];
|
|
});
|
|
// l.4 MEDIU-C: data raportului. O zi care NU a venit e refuzata (fara raport si fara plic cu createdAt in viitor); o zi din TRECUT nu
|
|
// poate purta nicio eticheta sprijinita pe ce a servit tinta dupa ea (varful / capul / blocul unei tranzactii date / scanul Core, citit la
|
|
// rulare). Prin Core, intr-un raport datat azi, un bloc cu ora la 400 de zile in viitor si un bloc servit fara ora -> NEMASURAT
|
|
const ZI_VIITOARE = '2030-01-01', ZI_TRECUTA = '2012-01-01';
|
|
const predicatViitor = (r) => r.cod === 1 && !r.scris && /REFUSED \(usage\)/.test(r.err) && /after the day of this run/.test(r.err);
|
|
await incearca('l. MEDIU-C: --data intr-o zi care nu a venit (2030-01-01) e REFUZATA (cod 1, nimic scris: niciun raport, niciun plic AIP-23 cu createdAt in viitor), in modul 1 si in modul 2; PLANTAREA INVERSA (refuzul scos) scrie raportul si plicul si iese ROSIE', async () => {
|
|
const r1 = ruleaza({ args: ['--mod', '1', '--adrese', LISTA_TOT], data: ZI_VIITOARE });
|
|
const r2 = await ruleazaEth(AICI, null, FER, { data: ZI_VIITOARE });
|
|
const det = `modul 1: cod ${r1.cod} ${r1.err.slice(0, 60)} | eth: cod ${r2.cod}`;
|
|
if (!(predicatViitor(r1) && predicatViitor(r2))) return [false, det];
|
|
const d = copie('l4-viitor'); const n = planteaza(path.join(d, 'raport.mjs'), 'if (date > azi) throw new Refuz(', 'if (false) throw new Refuz(');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const p = ruleaza({ dir: d, args: ['--mod', '1', '--adrese', LISTA_TOT], data: ZI_VIITOARE });
|
|
return [!predicatViitor(p) && p.cod === 0 && p.scris, `${det} | copia fara refuz: cod ${p.cod}, scris ${p.scris}`];
|
|
});
|
|
const txidsFix = Object.keys(bitcoindFix.raspunsuri).filter((k) => k.startsWith('getrawtransaction:')).map((k) => k.slice('getrawtransaction:'.length));
|
|
const ruleazaTrecut = async (dir, fel) => {
|
|
const fx = { electrum: 'electrum.json', esplora: 'esplora.json', bitcoind: 'bitcoind.json', 'bitcoind-fara': 'bitcoind.json', eth: 'eth-rpc.json' }[fel];
|
|
const m = await porneste(fel.startsWith('bitcoind') ? 'bitcoind' : fel, path.join(FIX, fx));
|
|
const args = fel === 'eth' ? ['--mod', '2', '--adrese', LISTA_ETH, '--eth-rpc', m.url, '--eth-blocks', FER] : ['--mod', '2', '--adrese', LISTA_BTC, ...(fel === 'electrum' ? ['--electrum', m.tinta] : fel === 'esplora' ? ['--esplora', m.url] : ['--bitcoind', m.url, ...(fel === 'bitcoind' ? ['--txids', scrieLista('l4-txids', txidsFix)] : [])])];
|
|
const r = ruleaza({ dir, args, tinte: m.tinta, data: ZI_TRECUTA }); m.opreste(); return r;
|
|
};
|
|
const predicatTrecut = (r) => r.cod === 2 && !!r.raport && r.raport.addresses.length > 0 && r.raport.addresses.every((a) => a.unmeasured && /serves blocks after the report date/.test(a.unmeasured)) && r.raport.summary.exposed === 0 && r.raport.summary.noOnChainEvidence === 0;
|
|
await incearca('l. MEDIU-C: --data intr-o zi din TRECUT (2012-01-01): Electrum si Esplora (varful din 2026), Ethereum (capul din 2026), Bitcoin Core cu --txids (blocuri din 2026) si FARA (scanul, citit la rulare) dau NEMASURAT "serves blocks after the report date" pe FIECARE adresa, si pe cele care altfel ies E; PLANTAREA INVERSA (data nejudecata) da E / F si iese ROSIE', async () => {
|
|
const feluri = ['electrum', 'esplora', 'eth', 'bitcoind', 'bitcoind-fara'];
|
|
const rez = []; for (const fel of feluri) rez.push(await ruleazaTrecut(AICI, fel));
|
|
const det = rez.map((r, i) => `${feluri[i]}: cod ${r.cod} ${r.raport ? r.raport.summary.verdict.replace(/address\(es\), /, '').slice(0, 60) : r.err.slice(0, 40)}`).join(' | ');
|
|
if (!rez.every(predicatTrecut)) return [false, det + ' | ' + (rez.find((r) => !predicatTrecut(r)) && rez.find((r) => !predicatTrecut(r)).raport ? detaliu(rez.find((r) => !predicatTrecut(r)).raport, rez.find((r) => !predicatTrecut(r)).raport.addresses[0].input).slice(0, 100) : '')];
|
|
const d = copie('l4-trecut'); const n = planteaza(path.join(d, 'clasifica.mjs'), PLANTA_DATA, 'if (false) return');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const p = await ruleazaTrecut(d, 'electrum');
|
|
return [!predicatTrecut(p) && !!p.raport && p.raport.summary.exposed > 0, `${det} | copia cu data nejudecata (electrum): ${p.raport ? p.raport.summary.verdict : p.err.slice(0, 60)}`];
|
|
});
|
|
// prin Core, intr-un raport datat AZI: cheltuirea care dezvaluie cheia e intr-un bloc al lantului activ cu ora la 400 de zile in
|
|
// viitor (mainnet nu primeste un antet cu peste 2 ore in viitor), sau servita fara ora blocului
|
|
const coreTimp = (() => {
|
|
const k = cheie('l4-core-timp'); const h = B.hash160(k.pub); const spk = p2pkhSpk(h); const adresa = b58check(0, h);
|
|
const f = finantare(spk, 'l4-core-timp'); const s = cheltuire(f.id, Buffer.concat([push(SIG), push(k.pub)]), []);
|
|
const baza = { ['scantxoutset:' + adresa]: { success: true, unspents: [] }, ['getrawtransaction:' + f.id]: verbose(f, blocSintetic('l4-f')) };
|
|
const cu = (bt) => ({ ...baza, ['getrawtransaction:' + s.id]: verbose(s, blocSintetic('l4-s'), 1, bt) });
|
|
return { adresa, ids: [f.id, s.id], s, bun: cu(1791753450), viitor: cu(Math.floor(Date.now() / 1000) + 400 * 86400), faraOra: cu(null) };
|
|
})();
|
|
await incearca('l. MEDIU-C: Bitcoin Core intr-un raport datat AZI: cheltuirea care dezvaluie cheia intr-un bloc cu ora la 400 de zile in viitor -> NEMASURAT "serves blocks after the report date: the block of transaction ..."; cu ora blocului in trecut -> E (controlul pozitiv)', async () => {
|
|
const r0 = await coreSintetic(AICI, 'l4-bun', coreTimp.bun, [coreTimp.adresa], coreTimp.ids), r1 = await coreSintetic(AICI, 'l4-viitor', coreTimp.viitor, [coreTimp.adresa], coreTimp.ids);
|
|
return [r0.cod === 0 && eticheta(r0.raport, coreTimp.adresa) === E_ && r1.cod === 2 && nemasuratCu(r1.raport, coreTimp.adresa, 'the target serves blocks after the report date: the block of transaction ' + coreTimp.s.id + ' given with --txids carries the time'), `ora in trecut: ${detaliu(r0.raport, coreTimp.adresa)} | ora in viitor: ${detaliu(r1.raport, coreTimp.adresa).slice(13, 120)}`];
|
|
});
|
|
await incearca('l. MEDIU-C: Bitcoin Core: o tranzactie data, pe lantul activ, servita FARA ora blocului (blocktime) -> NEMASURAT numit (nu poate fi asezata fata de data raportului), nu E; PLANTAREA INVERSA (lipsa orei ignorata) da E si iese ROSIE', async () => {
|
|
const pred = (r) => r.cod === 2 && nemasuratCu(r.raport, coreTimp.adresa, 'without the time of that block (blocktime)');
|
|
const r1 = await coreSintetic(AICI, 'l4-fara-ora', coreTimp.faraOra, [coreTimp.adresa], coreTimp.ids);
|
|
if (!pred(r1)) return [false, detaliu(r1.raport, coreTimp.adresa)];
|
|
const d = copie('l4-fara-ora'); const n = planteaza(path.join(d, 'clasifica.mjs'), PLANTA_BLOCKTIME, 'if (false) return numaiTinta(');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const p = await coreSintetic(d, 'l4-fara-ora-inv', coreTimp.faraOra, [coreTimp.adresa], coreTimp.ids);
|
|
return [!pred(p) && eticheta(p.raport, coreTimp.adresa) === E_, `${detaliu(r1.raport, coreTimp.adresa).slice(13, 110)} | copia care ignora lipsa orei: ${eticheta(p.raport, coreTimp.adresa)}`];
|
|
});
|
|
// l.5 MIC-D: o linie p2pk: si cheia ei in CEALALTA codificare / iesirea ei BIP-86. Trei chei sintetice, fiecare expusa NUMAI acolo:
|
|
// D1 P2PKH-ul formei necomprimate cheltuit (cheia necomprimata in scriptSig) pentru linia comprimata; D2 o iesire P2PK cu forma
|
|
// comprimata pentru linia necomprimata; D3 iesirea P2TR BIP-86 a cheii pentru linia comprimata. A patra cheie, fara istoric: F cu cele
|
|
// 12 surori citite numite, si randul N8 numeste numai ce nu se poate deriva
|
|
const ist = (() => {
|
|
const k1 = B.codificariCheie(cheie('l5-d1').pub), k2 = B.codificariCheie(cheie('l5-d2').pub), k3 = B.codificariCheie(cheie('l5-d3').pub), k4 = B.codificariCheie(cheie('l5-f').pub);
|
|
const spk1 = p2pkhSpk(B.hash160(k1.uncompressed)), spk2 = Buffer.concat([push(k2.compressed), Buffer.from([0xac])]), spk3 = Buffer.concat([Buffer.from([0x51, 0x20]), B.cheieIesireBip86(k3.P)]);
|
|
const f1 = finantare(spk1, 'l5-d1'), s1 = cheltuire(f1.id, Buffer.concat([push(SIG), push(k1.uncompressed)]), []), f2 = finantare(spk2, 'l5-d2'), f3 = finantare(spk3, 'l5-d3');
|
|
const linii = { d1: 'p2pk:' + B.hex(k1.compressed), d2: 'p2pk:' + B.hex(k2.uncompressed), d3: 'p2pk:' + B.hex(k3.compressed), f: 'p2pk:' + B.hex(k4.compressed) };
|
|
const el = (spk, l) => { const r = { ['blockchain.scripthash.get_history:' + B.scripthash(spk)]: l.map((x) => ({ tx_hash: x.id, height: x.height })) }; for (const x of l) r['blockchain.transaction.get:' + x.id] = x.raw.toString('hex'); return r; };
|
|
const es = (spk, l, chelt) => { const sh = B.hex(B.sha256(spk)); return { ['/scripthash/' + sh]: { scripthash: sh, chain_stats: { funded_txo_count: 1, funded_txo_sum: 100000, spent_txo_count: chelt, spent_txo_sum: 0, tx_count: l.length }, mempool_stats: { funded_txo_count: 0, funded_txo_sum: 0, spent_txo_count: 0, spent_txo_sum: 0, tx_count: 0 } }, ['/scripthash/' + sh + '/txs']: [...l].sort((a, b) => b.height - a.height).map(esploraTx) }; };
|
|
return { spk1, spk2, spk3, linii, lista: scrieLista('l5-p2pk', Object.values(linii)),
|
|
el: fisierSupra('l5-el', { ...el(spk1, [{ ...f1, height: 900000 }, { ...s1, height: 900001 }]), ...el(spk2, [{ ...f2, height: 900002 }]), ...el(spk3, [{ ...f3, height: 900003 }]) }),
|
|
es: fisierSupra('l5-es', { ...es(spk1, [{ ...f1, height: 900000 }, { ...s1, height: 900001 }], 1), ...es(spk2, [{ ...f2, height: 900002 }], 0), ...es(spk3, [{ ...f3, height: 900003 }], 0) }) };
|
|
})();
|
|
const predicatP2pk = (r) => !!r.raport && eticheta(r.raport, ist.linii.d1) === E_ && dovada(r.raport, ist.linii.d1, 'the same key in its uncompressed encoding, under P2PKH script:' + B.hex(ist.spk1) + ': public key revealed in the scriptSig')
|
|
&& eticheta(r.raport, ist.linii.d2) === E_ && dovada(r.raport, ist.linii.d2, 'the same key in its compressed encoding, under P2PK script:' + B.hex(ist.spk2) + ':') && dovada(r.raport, ist.linii.d2, 'P2PK output: the public key is the script')
|
|
&& eticheta(r.raport, ist.linii.d3) === E_ && dovada(r.raport, ist.linii.d3, 'a P2TR output with no script tree (BIP-86) whose internal key is this key, P2TR script:' + B.hex(ist.spk3) + ':') && nevazut(r.raport, ist.linii.d3, /output key Q = P \+ t\*G .*not this key itself/)
|
|
&& eticheta(r.raport, ist.linii.f) === F_ && (gaseste(r.raport, ist.linii.f).evidence.find((e) => e.startsWith('sibling scripts of the same key read through the same target')) || '').split(' confirmed transaction(s))').length - 1 === 12
|
|
&& nevazut(r.raport, ist.linii.f, /^the same key under another script type that cannot be derived from this input: a P2TR output whose key commits to a script tree, a P2SH or P2WSH script holding the key, and a bare-multisig output holding it/) && !gaseste(r.raport, ist.linii.f).notSeen.some((n) => /other encoding|a P2TR output or script/.test(n));
|
|
await incearca('l. MIC-D: o linie p2pk: si aceeasi cheie in CEALALTA codificare (P2PKH-ul formei necomprimate cheltuit; o iesire P2PK comprimata) sau in iesirea ei P2TR BIP-86: E cu sora numita, prin Electrum si Esplora; o cheie fara istoric: F cu cele 12 surori citite numite, iar randul N8 numeste numai ce nu se poate deriva (P2TR cu arbore, P2SH/P2WSH, multisig); PLANTAREA INVERSA (numai HASH160 al codificarii date, ca inainte) da F si iese ROSIE', async () => {
|
|
const r1 = await ruleazaCu(AICI, 'electrum', ist.el, ist.lista), r2 = await ruleazaCu(AICI, 'esplora', ist.es, ist.lista);
|
|
const det = [r1, r2].map((r, i) => `${['electrum', 'esplora'][i]}: ${['d1', 'd2', 'd3', 'f'].map((k) => k + '=' + eticheta(r.raport, ist.linii[k]).slice(0, 8)).join(' ')}`).join(' | ');
|
|
if (!(predicatP2pk(r1) && predicatP2pk(r2))) return [false, det];
|
|
const d = copie('l5-p2pk'); const n = planteaza(path.join(d, 'bitcoin.mjs'), "if (t.type === 'p2pk') {", 'if (false) {');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const p = await ruleazaCu(d, 'electrum', ist.el, ist.lista);
|
|
return [!predicatP2pk(p) && ['d1', 'd2', 'd3'].every((k) => eticheta(p.raport, ist.linii[k]) === F_), `${det} | copia cu surorile de dinainte: ${['d1', 'd2', 'd3'].map((k) => k + '=' + eticheta(p.raport, ist.linii[k]).slice(0, 8)).join(' ')}`];
|
|
});
|
|
// l.6 MIC-E: indexul numara in mempool o IESIRE a unui P2TR fara iesiri pe lant, iar pagina nu o poarta; si numara o TRANZACTIE din
|
|
// mempool a unei adrese P2PKH pe care pagina nu o poarta. Fiecare cu controlul ei (pagina o poarta) si plantarea ei inversa
|
|
const mpE = (() => {
|
|
const prog = crypto.createHash('sha256').update('AERE-SINTETIC l6 p2tr').digest(); const adr = B.segwitEncode('bc', 1, prog); const f = finantare(Buffer.concat([Buffer.from([0x51, 0x20]), prog]), 'l6-p2tr');
|
|
const k = cheie('l6-p2pkh'); const h = B.hash160(k.pub); const adrP = b58check(0, h); const fc = finantare(p2pkhSpk(h), 'l6-p2pkh-conf'), fm = finantare(p2pkhSpk(h), 'l6-p2pkh-mempool');
|
|
const zero = { funded_txo_count: 0, funded_txo_sum: 0, spent_txo_count: 0, spent_txo_sum: 0, tx_count: 0 }, unu = { funded_txo_count: 1, funded_txo_sum: 100000, spent_txo_count: 0, spent_txo_sum: 0, tx_count: 1 };
|
|
const sTr = { address: adr, chain_stats: zero, mempool_stats: unu }, sP = { address: adrP, chain_stats: unu, mempool_stats: unu };
|
|
return { adr, adrP, listaTr: scrieLista('l6-p2tr', [adr]), listaP: scrieLista('l6-p2pkh', [adrP]),
|
|
trNeservit: fisierSupra('l6-tr-neservit', { ['/address/' + adr]: sTr, ['/address/' + adr + '/txs']: [] }), trServit: fisierSupra('l6-tr-servit', { ['/address/' + adr]: sTr, ['/address/' + adr + '/txs']: [esploraTx({ ...f, height: 0 })] }),
|
|
pNeservit: fisierSupra('l6-p-neservit', { ['/address/' + adrP]: sP, ['/address/' + adrP + '/txs']: [esploraTx({ ...fc, height: 5 })] }), pServit: fisierSupra('l6-p-servit', { ['/address/' + adrP]: sP, ['/address/' + adrP + '/txs']: [esploraTx({ ...fm, height: 0 }), esploraTx({ ...fc, height: 5 })] }) };
|
|
})();
|
|
const predicatIesireMempool = (r) => r.cod === 2 && nemasuratCu(r.raport, mpE.adr, 'the target counts 1 output(s) of this script in its mempool (mempool_stats) and its transaction page carried 0 unconfirmed output(s) of it');
|
|
await incearca('l. MIC-E: Esplora numara in mempool o IESIRE a unui P2TR fara iesiri pe lant (mempool_stats.funded_txo_count 1) pe care pagina nu o poarta -> NEMASURAT numit, nu F "no output exists yet"; pagina care o poarta -> NEMASURAT (iesirea din mempool, controlul); PLANTAREA INVERSA (regula iesirilor numarate scoasa, cu plasa tranzactiilor) da F si iese ROSIE', async () => {
|
|
const r1 = await ruleazaCu(AICI, 'esplora', mpE.trNeservit, mpE.listaTr), r0 = await ruleazaCu(AICI, 'esplora', mpE.trServit, mpE.listaTr);
|
|
const det = `nepurtata: ${detaliu(r1.raport, mpE.adr).slice(13, 110)} | purtata: ${detaliu(r0.raport, mpE.adr).slice(13, 80)}`;
|
|
if (!(predicatIesireMempool(r1) && r0.cod === 2 && nemasuratCu(r0.raport, mpE.adr, "output(s) of this script are in the target's mempool, unconfirmed"))) return [false, det];
|
|
const d = copie('l6-iesiri'); const n = planteaza(path.join(d, 'clasifica.mjs'), PLANTA_IESIRI_MEMPOOL, 'if (false) return nem('), n2 = planteaza(path.join(d, 'clasifica.mjs'), PLASA_TX_MEMPOOL, 'if (false) return nem(');
|
|
if (n !== 1 || n2 !== 1) return `tinta plantarii apare de ${n} / ${n2} ori`;
|
|
const p = await ruleazaCu(d, 'esplora', mpE.trNeservit, mpE.listaTr);
|
|
return [!predicatIesireMempool(p) && eticheta(p.raport, mpE.adr) === F_, `${det} | copia fara regula: ${detaliu(p.raport, mpE.adr).slice(0, 40)}`];
|
|
});
|
|
const predicatTxMempool = (r) => r.cod === 2 && nemasuratCu(r.raport, mpE.adrP, 'the target counts 1 transaction(s) of this address in its mempool (mempool_stats) and its transaction page carried 0');
|
|
await incearca('l. MIC-E: Esplora numara in mempool o TRANZACTIE a unei adrese P2PKH (mempool_stats.tx_count 1) pe care pagina nu o poarta -> NEMASURAT numit; pagina care o poarta (o plata primita) -> F (controlul pozitiv); PLANTAREA INVERSA (regula tranzactiilor numarate scoasa) da F si iese ROSIE', async () => {
|
|
const r1 = await ruleazaCu(AICI, 'esplora', mpE.pNeservit, mpE.listaP), r0 = await ruleazaCu(AICI, 'esplora', mpE.pServit, mpE.listaP);
|
|
const det = `nepurtata: ${detaliu(r1.raport, mpE.adrP).slice(13, 110)} | purtata: ${detaliu(r0.raport, mpE.adrP)}`;
|
|
if (!(predicatTxMempool(r1) && r0.cod === 0 && eticheta(r0.raport, mpE.adrP) === F_)) return [false, det];
|
|
const d = copie('l6-tx'); const n = planteaza(path.join(d, 'clasifica.mjs'), PLASA_TX_MEMPOOL, 'if (false) return nem(');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const p = await ruleazaCu(d, 'esplora', mpE.pNeservit, mpE.listaP);
|
|
return [!predicatTxMempool(p) && eticheta(p.raport, mpE.adrP) === F_, `${det} | copia fara regula: ${detaliu(p.raport, mpE.adrP).slice(0, 40)}`];
|
|
});
|
|
// l.7: fiecare verdict Electrum / Esplora numeste lantul servit (al doilea rand de dovada), pe vectorii reali
|
|
await incearca('l. MEDIU-A: pe vectorii reali (fixturile, geneza mainnet), fiecare verdict Electrum si Esplora numeste lantul: al doilea rand de dovada e "chain served: Bitcoin mainnet (genesis block 000000000019d668...; at height 969696 the block ..., the checkpoint this tool holds; ...; whether it is the chain with the most work is not verified)" (r8: punctul de control si ce nu se verifica)', async () => {
|
|
const rez = []; for (const fel of ['electrum', 'esplora']) rez.push(await ruleazaCu(AICI, fel, null, LISTA_BTC));
|
|
const P_ = B.RETELE_BTC.mainnet.puncte[B.RETELE_BTC.mainnet.puncte.length - 1];
|
|
const linie = 'chain served: Bitcoin mainnet (genesis block ' + B.RETELE_BTC.mainnet.geneza + '; at height ' + P_.height + ' the block ' + P_.hash + ', the checkpoint this tool holds; the tip header states a target within its proof-of-work limit; whether it is the chain with the most work is not verified)';
|
|
return [rez.every((r) => r.cod === 0 && r.raport.addresses.length === 8 && r.raport.addresses.every((a) => a.evidence[1] === linie)), rez.map((r, i) => `${['electrum', 'esplora'][i]}: cod ${r.cod}, ${r.raport ? r.raport.addresses.filter((a) => a.evidence[1] === linie).length : 0}/8`).join(' | ')];
|
|
});
|
|
// ================================================================ m. runda 8 (revizia rundei 7, scratchpad c12rev8): starea Ethereum
|
|
// legata de capul judecat (MEDIU-1), un lant DESPARTIT de Bitcoin cu aceeasi geneza si limita (MEDIU-2), lantul nodului Bitcoin Core,
|
|
// numaratorile Esplora citite numai ca numere, iesirile numarate si tranzactiile care nu ating scriptul (MIC). Fiecare cu plantarea ei
|
|
// inversa (defectul pus inapoi intr-o copie)
|
|
const PLANTA_PUNCT = 'if (c.servit !== c.asteptat) return {';
|
|
const PLANTA_SUB = 'if (c.sub !== undefined) return {';
|
|
const PLANTA_ETH_LATEST = 'const st = await stareLaCap(rpc, a.address, laCap, radacina, varf);';
|
|
const PLANTA_ETH_DOVADA = 'const cont = E.contDinDovada(radacina, adresa, dovada);';
|
|
const PLANTA_ETH_COD = "if (E.keccak(Buffer.from(code.slice(2), 'hex')).toString('hex') !== cont.codeHash) return {";
|
|
const PLANTA_CORE_LANT = 'obs.chain = o.lantCore;';
|
|
const PLANTA_NUMARATOR = 'const numarator = (x) => (Number.isSafeInteger(x) && x >= 0 ? x : null);';
|
|
const PLANTA_MEMPOOL_NECITIT = 'if (!revel.length && !expusPrinTip && obs.mempoolNecitit) return nem(';
|
|
const PLANTA_FINANTATE = 'if (!revel.length && !expusPrinTip && Number.isFinite(obs.fundedOutputsServed) && obs.fundedOutputsServed > outs.length) return nem(';
|
|
const PLANTA_NEATINSE = 'export function neatinse(t, txs) {';
|
|
const PC = B.RETELE_BTC.mainnet.puncte, PC_SUS = PC[PC.length - 1];
|
|
// m.1 MEDIU-1: o macheta Ethereum care raspunde ca un echilibrator cu o masina in urma. Fereastra e numai capul (servit fara tranzactii),
|
|
// ca o tranzactie gasita sa nu acopere eticheta. Cinci conturi, fiecare pe drumul lui; in fixtura, starea capului e trie-ul conturilor
|
|
// fixturii (sintetic, _sursa.nota)
|
|
const CAP_NR = Number(BigInt(CAP_ETH)); const TS_CAP = ethFixR[`eth_getBlockByNumber:${JSON.stringify([CAP_ETH, false])}`].timestamp;
|
|
const ALT1 = '0x' + '21'.repeat(20), ALT2 = '0x' + '22'.repeat(20);
|
|
const stareStraina = E.stareConturi([{ address: ALT1, nonce: 0, code: '0x' }, { address: '0x' + '23'.repeat(20), nonce: 3, code: '0x' }]);
|
|
const supraM1 = fisierSupra('m1-eth', {
|
|
[`eth_getBlockByNumber:${JSON.stringify([CAP_ETH, true])}`]: { number: CAP_ETH, timestamp: TS_CAP, hash: '0x' + '55'.repeat(32), transactions: [] },
|
|
// expeditorul: masina B, ramasa in urma, nu are blocul capului ("header not found" la numarul lui, cum raspunde geth) si are la
|
|
// 'latest' starea ei veche (nonce 0, fara cod); la masina A contul a trimis o tranzactie (in starea fixturii, nonce 1)
|
|
[`eth_getProof:${JSON.stringify([ETHV.expeditor, [], CAP_ETH])}`]: { _eroare: { code: -32000, message: 'header not found' } },
|
|
[`eth_getTransactionCount:${JSON.stringify([ETHV.expeditor, 'latest'])}`]: '0x0', [`eth_getCode:${JSON.stringify([ETHV.expeditor, 'latest'])}`]: '0x',
|
|
// contul fara iesiri: invers, la 'latest' o stare de DUPA cap (un designator EIP-7702 pus mai tarziu); la cap, dovada fixturii
|
|
[`eth_getTransactionCount:${JSON.stringify([ETHV.primitor, 'latest'])}`]: '0x1', [`eth_getCode:${JSON.stringify([ETHV.primitor, 'latest'])}`]: '0xef0100' + '11'.repeat(20),
|
|
// contractul: codul servit la cap NU se hashuieste la hash-ul de cod dovedit
|
|
[`eth_getCode:${JSON.stringify([ETHV.contract, CAP_ETH])}`]: '0x6060',
|
|
// ALT1: dovada ALTEI stari (nu duce la radacina capului), cu campurile servite langa ea spunand nonce 0, fara cod
|
|
[`eth_getProof:${JSON.stringify([ALT1, [], CAP_ETH])}`]: stareStraina.dovada(ALT1),
|
|
// ALT2: un furnizor fara eth_getProof
|
|
[`eth_getProof:${JSON.stringify([ALT2, [], CAP_ETH])}`]: { _eroare: { code: -32601, message: 'the method eth_getProof does not exist/is not available' } },
|
|
});
|
|
const LISTA_M1 = scrieLista('m1-eth', [ETHV.expeditor, ETHV.primitor, ETHV.contract, ETHV.autoritate7702, ALT1, ALT2]);
|
|
const ruleazaM1 = async (dir) => { const m = await porneste('eth', path.join(FIX, 'eth-rpc.json'), ['--suprascrie', supraM1]); const r = ruleaza({ dir, args: ['--mod', '2', '--adrese', LISTA_M1, '--eth-rpc', m.url, '--eth-blocks', '1'], tinte: m.tinta }); m.opreste(); return r; };
|
|
const m1Masini = (r) => r.cod === 2 && nemasuratCu(r.raport, ETHV.expeditor, `did not serve the account proof (eth_getProof) at its head block ${CAP_NR}`) && nemasuratCu(r.raport, ETHV.expeditor, 'header not found');
|
|
const m1Invers = (r) => eticheta(r.raport, ETHV.primitor) === F_ && dovada(r.raport, ETHV.primitor, `no code and nonce 0 at head block ${CAP_NR}, both proven under its state root`);
|
|
const m1Cod = (r) => nemasuratCu(r.raport, ETHV.contract, `the code served at the head block ${CAP_NR} does not hash to the code hash proven under the state root of that block`);
|
|
const m1Straina = (r) => nemasuratCu(r.raport, ALT1, `the account proof served at the head block ${CAP_NR} does not lead to the state root of that block`);
|
|
const m1FaraDovada = (r) => nemasuratCu(r.raport, ALT2, 'did not serve the account proof (eth_getProof)') && nemasuratCu(r.raport, ALT2, '-32601');
|
|
const m1Pozitiv = (r) => eticheta(r.raport, ETHV.autoritate7702) === E_ && dovada(r.raport, ETHV.autoritate7702, `EIP-7702 delegation designator in the account code at head block ${CAP_NR}`);
|
|
await incearca('m. MEDIU-1 (r8): starea unui cont Ethereum se citeste NUMAI din eth_getProof la numarul capului, verificat fata de radacina de stare a blocului capului: o masina in urma ("header not found" la cap, nonce 0 la \'latest\') -> NEMASURAT, nu F; o stare de dupa cap la \'latest\' (designator EIP-7702) -> F pe starea capului, nu E; un cod care nu se hashuieste la hash-ul dovedit, o dovada a altei stari si un furnizor fara eth_getProof -> NEMASURAT numit; designatorul real -> E (controlul); PLANTARILE INVERSE (starea la \'latest\'; campurile servite in locul dovezii; codul nehashuit) ies ROSII', async () => {
|
|
const r = await ruleazaM1(AICI);
|
|
const det = ['expeditor', 'primitor', 'contract', 'autoritate7702'].map((k) => `${k}: ${detaliu(r.raport, ETHV[k]).slice(0, 70)}`).join(' | ') + ` | alt1: ${detaliu(r.raport, ALT1).slice(13, 80)} | alt2: ${detaliu(r.raport, ALT2).slice(13, 80)}`;
|
|
if (!(m1Masini(r) && m1Invers(r) && m1Cod(r) && m1Straina(r) && m1FaraDovada(r) && m1Pozitiv(r))) return [false, det];
|
|
const d1 = copie('m1-latest'), d2 = copie('m1-campuri'), d3 = copie('m1-cod');
|
|
const n1 = planteaza(path.join(d1, 'adaptoare.mjs'), PLANTA_ETH_LATEST, "const st = { code: await rpc('eth_getCode', [a.address, 'latest']), nonce: await rpc('eth_getTransactionCount', [a.address, 'latest']) };");
|
|
const n2 = planteaza(path.join(d2, 'adaptoare.mjs'), PLANTA_ETH_DOVADA, 'const cont = { nonce: BigInt(dovada.nonce), codeHash: String(dovada.codeHash).slice(2), absent: false };');
|
|
const n3 = planteaza(path.join(d3, 'adaptoare.mjs'), PLANTA_ETH_COD, 'if (false) return {');
|
|
if (n1 !== 1 || n2 !== 1 || n3 !== 1) return `tintele plantarii apar de ${n1} / ${n2} / ${n3} ori`;
|
|
const p1 = await ruleazaM1(d1), p2 = await ruleazaM1(d2), p3 = await ruleazaM1(d3);
|
|
return [!m1Masini(p1) && eticheta(p1.raport, ETHV.expeditor) === F_ && !m1Invers(p1) && eticheta(p1.raport, ETHV.primitor) === E_ && !m1Straina(p2) && eticheta(p2.raport, ALT1) === F_ && !m1Cod(p3) && eticheta(p3.raport, ETHV.contract) === F_,
|
|
`${det} | starea la 'latest': expeditor ${eticheta(p1.raport, ETHV.expeditor)}, primitor ${eticheta(p1.raport, ETHV.primitor)}; campurile servite: alt1 ${eticheta(p2.raport, ALT1)}; codul nehashuit: contract ${eticheta(p3.raport, ETHV.contract)}`];
|
|
});
|
|
// m.2 MEDIU-2: un lant DESPARTIT de Bitcoin (Bitcoin Cash, Bitcoin SV, eCash: aceeasi geneza, aceeasi limita de dovada de lucru) serveste
|
|
// la inaltimea punctului de control ALT bloc. Antetul REAL al blocului 969686 (VARF_VECHI, din lantul validat de C6) tine locul blocului
|
|
// forkului acolo: un antet cu dovada de lucru buna si alt hash (un antet BCH real nu se poate produce offline; garda compara hash-ul de la
|
|
// acea inaltime cu punctul uneltei, deci orice alt bloc o pune la lucru la fel)
|
|
const supraFork = (fel) => (fel === 'electrum' ? { ['blockchain.block.header:' + PC_SUS.height]: VARF_VECHI.hex } : { ['/block-height/' + PC_SUS.height]: VARF_VECHI.hash });
|
|
const MOTIV_FORK = `the target serves another chain: one that shares the genesis block of Bitcoin mainnet and split from it before height ${PC_SUS.height}, where it serves the block ${VARF_VECHI.hash} and Bitcoin mainnet has the block ${PC_SUS.hash}`;
|
|
const predicatFork = (r, adr = A.p2pkh_necheltuit) => r.cod === 2 && nemasuratCu(r.raport, adr, MOTIV_FORK) && dovada(r.raport, adr, `chain served: genesis block ${B.RETELE_BTC.mainnet.geneza}, that of Bitcoin mainnet, but at height ${PC_SUS.height}`) && !dovada(r.raport, adr, 'chain served: Bitcoin mainnet');
|
|
await incearca('m. MEDIU-2 (r8): o tinta a unui lant DESPARTIT de Bitcoin (geneza mainnet, limita mainnet, varful real 971000) care serveste la inaltimea punctului de control 969696 ALT bloc -> NEMASURAT "serves another chain: ... split from it before height 969696", pe Electrum si pe Esplora, fara "chain served: Bitcoin mainnet"; varful real 969686 alege punctul 967680 si il numeste (controlul alegerii); PLANTAREA INVERSA (punctul necomparat) da F si iese ROSIE', async () => {
|
|
const rez = []; for (const fel of ['electrum', 'esplora']) rez.push(await ruleazaCu(AICI, fel, fisierSupra('m2-fork-' + fel, supraFork(fel)), LISTA_NECH));
|
|
const rV = await ruleazaCu(AICI, 'electrum', fisierSupra('m2-vechi', varfSupra('electrum', VARF_VECHI)), LISTA_NECH);
|
|
const linieV = `at height ${PC[0].height} the block ${PC[0].hash}, the checkpoint this tool holds`;
|
|
const det = rez.map((r, i) => `${['electrum', 'esplora'][i]}: ${detaliu(r.raport, A.p2pkh_necheltuit).slice(13, 110)}`).join(' | ') + ` | varful 969686: punctul ${PC[0].height} numit: ${dovada(rV.raport, A.p2pkh_necheltuit, linieV)}`;
|
|
if (!(rez.every((r) => predicatFork(r)) && dovada(rV.raport, A.p2pkh_necheltuit, linieV) && nemasuratCu(rV.raport, A.p2pkh_necheltuit, 'target behind the chain'))) return [false, det];
|
|
const d = copie('m2-punct'); const n = planteaza(path.join(d, 'clasifica.mjs'), PLANTA_PUNCT, 'if (false) return {');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const p = await ruleazaCu(d, 'electrum', fisierSupra('m2-fork-inv', supraFork('electrum')), LISTA_NECH);
|
|
return [!predicatFork(p) && eticheta(p.raport, A.p2pkh_necheltuit) === F_, `${det} | copia cu punctul necomparat: ${detaliu(p.raport, A.p2pkh_necheltuit).slice(0, 40)}`];
|
|
});
|
|
// m.3 MEDIU-2: un lant care se termina SUB cel mai jos punct de control, cu geneza si limita mainnet si un varf proaspat: nimic nu poate
|
|
// deosebi acolo un lant despartit. Clasificarea direct, fara retea (un antet minat la dificultatea mainnet nu se poate face aici)
|
|
const obsSub = () => ({ outputs: [{ txid: 'aa'.repeat(32), vout: 0, height: 900000 }], inputs: [], spentOutputs: 0, txCount: 1, unconfirmedCount: 0, tip: { height: 960000, time: ORA_PROASPATA, hash: '00'.repeat(32), bits: 0x1d00ffff }, chain: { geneza: B.RETELE_BTC.mainnet.geneza, retea: 'mainnet', control: { sub: PC[0].height, inaltime: 960000 } }, siblings: [] });
|
|
const CTX_FIX = { referinta: Date.parse('2026-10-12T00:00:00Z'), referintaText: 'the end of the report date 2026-10-11' };
|
|
await incearca('m. MEDIU-2 (r8): un lant cu geneza si limita mainnet care se termina SUB cel mai jos punct de control (960000 < 967680), cu varful proaspat -> NEMASURAT "below height 967680, the lowest checkpoint" (clasificarea direct); PLANTAREA INVERSA (regula scoasa) da F si iese ROSIE', async () => {
|
|
const t = B.decodeazaIntrareBitcoin(A.p2pkh_necheltuit); const C0 = await import(pathToFileURL(path.join(AICI, 'clasifica.mjs')).href);
|
|
const pred = (x) => x.label === null && /below height 967680, the lowest checkpoint this tool holds for Bitcoin mainnet/.test(String(x.unmeasured));
|
|
const r = C0.clasificaBitcoinObservat(t, obsSub(), 2, CTX_FIX);
|
|
if (!pred(r)) return [false, String(r.unmeasured || r.label)];
|
|
const d = copie('m3-sub'); const n = planteaza(path.join(d, 'clasifica.mjs'), PLANTA_SUB, 'if (false) return {');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const Cp = await import(pathToFileURL(path.join(d, 'clasifica.mjs')).href);
|
|
const p = Cp.clasificaBitcoinObservat(t, obsSub(), 2, CTX_FIX);
|
|
return [!pred(p) && p.label === F_, `${String(r.unmeasured).slice(13, 120)} | copia fara regula: ${p.label || p.unmeasured}`];
|
|
});
|
|
// m.4 MIC (r8): Bitcoin Core e intrebat ce lant serveste, O DATA pe raport (getblockhash 0, getblockcount, getblockhash la punctul de
|
|
// control). Linia p2pk: a cheii blocului 1 iese E din scan pe fixtura (iesirea necheltuita); pe un nod regtest sau al unui lant despartit,
|
|
// nicio eticheta
|
|
const LISTA_P2PK = [A.p2pk];
|
|
const predCoreReg = (r) => r.cod === 2 && nemasuratCu(r.raport, A.p2pk, 'the target serves another chain: Bitcoin regtest, and this is a p2pk: line, which this tool reads on Bitcoin mainnet only');
|
|
await incearca('m. MIC (r8): Bitcoin Core e intrebat ce lant serveste (o data pe raport): pe fixtura linia p2pk: iese E cu lantul numit (controlul); un nod REGTEST -> NEMASURAT "serves another chain: Bitcoin regtest"; un nod al unui lant despartit (alt bloc la 969696) -> NEMASURAT "split from it before height 969696"; PLANTAREA INVERSA (lantul nodului netrecut clasificarii) da E pe nodul regtest si iese ROSIE', async () => {
|
|
const r0 = await coreSintetic(AICI, 'm4-mainnet', {}, LISTA_P2PK, []), r1 = await coreSintetic(AICI, 'm4-regtest', { 'getblockhash:0': B.RETELE_BTC.regtest.geneza, getblockcount: 300 }, LISTA_P2PK, []), r2 = await coreSintetic(AICI, 'm4-fork', { ['getblockhash:' + PC_SUS.height]: VARF_VECHI.hash }, LISTA_P2PK, []);
|
|
const det = `fixtura: ${detaliu(r0.raport, A.p2pk).slice(0, 40)} | regtest: ${detaliu(r1.raport, A.p2pk).slice(13, 90)} | fork: ${detaliu(r2.raport, A.p2pk).slice(13, 90)}`;
|
|
if (!(eticheta(r0.raport, A.p2pk) === E_ && dovada(r0.raport, A.p2pk, `chain served: Bitcoin mainnet (genesis block ${B.RETELE_BTC.mainnet.geneza}; at height ${PC_SUS.height} the block ${PC_SUS.hash}, the checkpoint this tool holds;`) && predCoreReg(r1) && predicatFork(r2, A.p2pk))) return [false, det];
|
|
const d = copie('m4-core'); const n = planteaza(path.join(d, 'adaptoare.mjs'), PLANTA_CORE_LANT, '');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const p = await coreSintetic(d, 'm4-regtest-inv', { 'getblockhash:0': B.RETELE_BTC.regtest.geneza, getblockcount: 300 }, LISTA_P2PK, []);
|
|
return [!predCoreReg(p) && eticheta(p.raport, A.p2pk) === E_, `${det} | copia fara lantul nodului: ${detaliu(p.raport, A.p2pk).slice(0, 40)}`];
|
|
});
|
|
// m.5 MIC (r8): ce serveste un index Esplora se citeste ca NUMERE si se compara: o numaratoare null (Number(null) e 0), mempool_stats lipsa
|
|
// sau cu o numaratoare negativa, un index care numara mai multe iesiri decat poarta paginile, o tranzactie care nu atinge scriptul. Cinci
|
|
// adrese sintetice, fiecare finantata o data (altfel F), fiecare pe drumul ei
|
|
const mic = (() => {
|
|
const zero = { funded_txo_count: 0, funded_txo_sum: 0, spent_txo_count: 0, spent_txo_sum: 0, tx_count: 0 }, unu = { funded_txo_count: 1, funded_txo_sum: 100000, spent_txo_count: 0, spent_txo_sum: 0, tx_count: 1 };
|
|
const adr = (et) => { const k = cheie('m5-' + et); const h = B.hash160(k.pub); const a = b58check(0, h); const f = finantare(p2pkhSpk(h), 'm5-' + et); return { a, f }; };
|
|
const nul = adr('nul'), memLipsa = adr('mem-lipsa'), memNeg = adr('mem-neg'), fin = adr('fin');
|
|
const progTr = crypto.createHash('sha256').update('AERE-SINTETIC m5 p2tr').digest(); const tr = B.segwitEncode('bc', 1, progTr);
|
|
const straina = finantare(p2pkhSpk(B.hash160(cheie('m5-straina').pub)), 'm5-straina');
|
|
const pag = (x, h = 7) => [esploraTx({ ...x.f, height: h })];
|
|
const r = {
|
|
['/address/' + nul.a]: { address: nul.a, chain_stats: { ...unu, tx_count: null, funded_txo_count: null, spent_txo_count: null }, mempool_stats: zero }, ['/address/' + nul.a + '/txs']: [],
|
|
['/address/' + memLipsa.a]: { address: memLipsa.a, chain_stats: unu }, ['/address/' + memLipsa.a + '/txs']: pag(memLipsa),
|
|
['/address/' + memNeg.a]: { address: memNeg.a, chain_stats: unu, mempool_stats: { ...zero, spent_txo_count: -1 } }, ['/address/' + memNeg.a + '/txs']: pag(memNeg),
|
|
['/address/' + fin.a]: { address: fin.a, chain_stats: { ...unu, funded_txo_count: 2 }, mempool_stats: zero }, ['/address/' + fin.a + '/txs']: pag(fin),
|
|
['/address/' + tr]: { address: tr, chain_stats: { ...zero, tx_count: 1 }, mempool_stats: zero }, ['/address/' + tr + '/txs']: [esploraTx({ ...straina, height: 7 })],
|
|
};
|
|
return { nul: nul.a, memLipsa: memLipsa.a, memNeg: memNeg.a, fin: fin.a, tr, lista: scrieLista('m5-esplora', [nul.a, memLipsa.a, memNeg.a, fin.a, tr]), es: fisierSupra('m5-es', r),
|
|
// aceeasi tranzactie straina in istoricul Electrum al P2TR-ului (fara iesiri)
|
|
el: suprascriereElectrum('m5-el', Buffer.concat([Buffer.from([0x51, 0x20]), progTr]), [{ ...straina, height: 7 }]), listaTr: scrieLista('m5-tr', [tr]) };
|
|
})();
|
|
const p5 = {
|
|
nul: (r) => nemasuratCu(r.raport, mic.nul, 'served chain_stats counts that are not counts') && nemasuratCu(r.raport, mic.nul, '"tx_count":null'),
|
|
memLipsa: (r) => nemasuratCu(r.raport, mic.memLipsa, 'served no mempool counts for this script that are counts (mempool_stats: absent)'),
|
|
memNeg: (r) => nemasuratCu(r.raport, mic.memNeg, 'served no mempool counts for this script that are counts (mempool_stats: [served: '),
|
|
tr: (r) => nemasuratCu(r.raport, mic.tr, 'transaction(s) that neither pay it nor spend it'),
|
|
fin: (r) => nemasuratCu(r.raport, mic.fin, 'the target counts 2 output(s) to this script on chain (chain_stats) but its pages carried 1'),
|
|
};
|
|
await incearca('m. MIC (r8): Esplora: o numaratoare null (chain_stats.tx_count, spent_txo_count), mempool_stats lipsa, o numaratoare mempool negativa, un index care numara 2 iesiri cand paginile poarta 1, o tranzactie in istoric care nu atinge scriptul (si prin Electrum) -> NEMASURAT numit fiecare, nu F; PLANTARILE INVERSE (Number(x); mempool necitit ignorat; iesirile numarate necomparate; tranzactiile straine acceptate) dau F si ies ROSII', async () => {
|
|
const r = await ruleazaCu(AICI, 'esplora', mic.es, mic.lista), re = await ruleazaCu(AICI, 'electrum', mic.el, mic.listaTr);
|
|
const det = Object.keys(p5).map((k) => `${k}: ${detaliu(r.raport, mic[k]).slice(13, 70)}`).join(' | ') + ` | electrum tr: ${detaliu(re.raport, mic.tr).slice(13, 70)}`;
|
|
if (!(r.cod === 2 && Object.values(p5).every((f) => f(r)) && p5.tr(re))) return [false, det];
|
|
const plantari = [['nul', 'adaptoare.mjs', PLANTA_NUMARATOR, 'const numarator = (x) => Number(x);'], ['memLipsa', 'clasifica.mjs', PLANTA_MEMPOOL_NECITIT, 'if (false) return nem('], ['fin', 'clasifica.mjs', PLANTA_FINANTATE, 'if (false) return nem('], ['tr', 'adaptoare.mjs', PLANTA_NEATINSE, 'export function neatinse(t, txs) { return [];']];
|
|
const rezP = [];
|
|
for (const [k, fis, vechi, nou] of plantari) {
|
|
const d = copie('m5-' + k.toLowerCase()); const n = planteaza(path.join(d, fis), vechi, nou); if (n !== 1) return `tinta plantarii ${k} apare de ${n} ori`;
|
|
const p = await ruleazaCu(d, 'esplora', mic.es, mic.lista); rezP.push([k, !p5[k](p) && eticheta(p.raport, mic[k]) === F_, eticheta(p.raport, mic[k])]);
|
|
}
|
|
return [rezP.every(([, ok]) => ok), `${det} | copiile plantate: ${rezP.map(([k, , e]) => k + '=' + e.slice(0, 8)).join(' ')}`];
|
|
});
|
|
});
|
|
|
|
// ================================================================ 6. textele publicabile
|
|
await pas('6. textele publicabile', async () => {
|
|
const fisiere = fs.readdirSync(AICI).filter((f) => /\.(mjs|md|json)$/.test(f) && f !== 'limbaj.mjs' && f !== 'proba-expunere.mjs');
|
|
const hits = fisiere.flatMap((f) => verificaLimbaj(fs.readFileSync(path.join(AICI, f), 'utf8')).map((h) => f + ': ' + h.word));
|
|
cer(`zero cuvinte interzise in cele ${fisiere.length} fisiere publicabile (limbaj.mjs, singurul care le numeste, si proba sunt excluse)`, da(fisiere.length >= 10 && hits.length === 0), hits.slice(0, 5).join('; '));
|
|
// poarta de limbaj: cratima si spatiul nu mai ascund un cuvant, iar compusele se prind scrise in orice fel; negatiile uneltei raman curate
|
|
await incearca('poarta de limbaj prinde formele pe care lista de dinainte le scapa (cu cratima inainte sau dupa, cu spatiu sau lipite: post-quantum-safe, PQ-safe, quantum resistant, quantumproof, fully-protected, protecting, well-secured, guaranteeing, guarantor, trust-less, ...) si lasa curate negatiile uneltei ("it does not protect", "protection", "unsafe", "safeguard"); PLANTAREA INVERSA (tiparul si lista de dinainte, intr-o copie) le scapa si iese ROSIE', async () => {
|
|
const forme = ['post-quantum-safe', 'PQ-safe', 'quantum resistant', 'post-quantum-resistant', 'quantum proof', 'quantumproof', 'fully-protected', 'protecting', 'well-secured', 'guaranteeing', 'guarantor', 'trust-less', 'quantum-safe-signatures', 'stored safely', 'safer', 'safety', 'a secure address', 'securely', 'quantum-secure'];
|
|
const curate = ['It does not protect, move or recover any coins', 'post-quantum readiness', 'unsafe', 'safeguard', 'a quantum computer', 'no on-chain evidence of exposure', 'protection', 'an optional profile for national security systems'];
|
|
const judeca = (vl) => forme.filter((f) => vl(f).length === 0).concat(curate.filter((c) => vl(c).length > 0).map((c) => 'murdar: ' + c));
|
|
const scapate = judeca(verificaLimbaj);
|
|
if (scapate.length) return [false, 'poarta: ' + scapate.join(', ')];
|
|
const d = copie('limbaj-vechi');
|
|
if (planteaza(path.join(d, 'limbaj.mjs'), 'const INTERZISE = [', "const INTERZISE = ['safe', 'protected', 'protects', 'secured', 'quantum-proof', 'quantum-resistant', 'unexposed', 'guarantee', 'guarantees', 'guaranteed', 'trustless', 'quantum-safe']; const INTERZISE_NOI = [") !== 1) return 'tinta plantarii (lista) nu apare o data';
|
|
if (planteaza(path.join(d, 'limbaj.mjs'), "const RX = new RegExp('(?<![A-Za-z0-9])(' + INTERZISE.join('|') + ')(?![A-Za-z0-9])', 'gi');", "const RX = new RegExp('(?<![A-Za-z0-9-])(' + INTERZISE.join('|') + ')(?![A-Za-z0-9-])', 'gi');") !== 1) return 'tinta plantarii (tiparul) nu apare o data';
|
|
const { verificaLimbaj: vlVechi } = await import(pathToFileURL(path.join(d, 'limbaj.mjs')).href);
|
|
const scapateVechi = judeca(vlVechi);
|
|
return [scapateVechi.length >= 17 && !scapateVechi.some((x) => x.startsWith('murdar')), `poarta: 0 scapate din ${forme.length}, ${curate.length} negatii curate | copia de dinainte scapa ${scapateVechi.length}: ${scapateVechi.slice(0, 4).join(', ')}...`];
|
|
});
|
|
// numele interne se DERIVA din inventar (adrese si nume de gazde) si se asambleaza din bucati, ca acest fisier sa poata fi scanat si el
|
|
let gazde = [], nume = [];
|
|
try { const cfg = JSON.parse(fs.readFileSync(path.join(RAD, 'monitoring', 'config.json'), 'utf8')); const l = (cfg.hosts && cfg.hosts.list) || []; gazde = l.map((h) => h.ip).filter(Boolean); nume = l.map((h) => h.name).filter(Boolean); } catch { gazde = []; }
|
|
const interne = [...gazde, ...nume, ['rpc', '2'].join(''), ['aere', 'infra'].join('-'), ['/opt', 'aere'].join('/'), ['aere', 'architect'].join('_')];
|
|
const tot = [...fs.readdirSync(AICI).filter((f) => /\.(mjs|md|txt|json)$/.test(f)).map((f) => path.join(AICI, f)), ...fs.readdirSync(FIX).map((f) => path.join(FIX, f))].map((p) => [path.basename(p), fs.readFileSync(p, 'utf8')]);
|
|
const scurgeri = tot.flatMap(([f, s]) => interne.filter((x) => s.includes(x)).map((x) => f + ': ' + x));
|
|
if (!gazde.length) cer('nimic din operarea noastra in dosar si in fixturi (scanarea dupa inventar)', 'NEMASURAT', 'no inventory beside this tool (a copy outside the development tree): the names to look for are not known here');
|
|
else cer(`nimic din operarea noastra in dosar si in fixturi (${gazde.length} adrese si ${nume.length} nume de gazde din inventar + nume interne, cautate in ${tot.length} fisiere, proba inclusa)`, da(gazde.length >= 9 && nume.length >= 9 && scurgeri.length === 0), scurgeri.join('; '));
|
|
const surseUnealta = ['raport.mjs', 'clasifica.mjs', 'adaptoare.mjs', 'bitcoin.mjs', 'ethereum.mjs', 'limbaj.mjs', 'noble.mjs'].map((f) => fs.readFileSync(path.join(AICI, f), 'utf8')).join('\n');
|
|
cer('sursele uneltei nu folosesc child_process, worker_threads, process.binding, internalBinding, lookupService sau module native (drumurile pe care paza le vede numai refuzandu-le, sau deloc)', da(!/child_process|worker_threads|\.node['"]|process\.dlopen|process\.binding|internalBinding|lookupService/.test(surseUnealta)));
|
|
cer('fisierele uneltei nu au fost atinse de proba (amprente egale inainte si dupa)', da(FISIERE_UNEALTA.every((f) => amprenta(path.join(AICI, f)) === amprenteInainte[f])));
|
|
// copiile-frate: git le ignora (o rulare omorata nu lasa fisiere neurmarite pe care `git add -A` sa le ia), si curatenia de la pornire
|
|
// sterge numai orfanele: un dosar al unui proces MORT dispare, unul al unui proces VIU (chiar aceasta proba) ramane
|
|
{
|
|
const et = 'copiile-frate .expunere-planta-* si .expunere-oglinda-* sunt ignorate de git (si un frate oarecare, controlul negativ, nu e)';
|
|
const rel = (n) => path.relative(RAD, path.join(AICI, '..', n)).split(path.sep).join('/');
|
|
// un FISIER din copie, nu "dosar/": git 2.53 raspunde 0 (ignorat) la orice cale cu bara la coada, pe o linie goala din .gitignore
|
|
// (masurat 2026-10-12, controlul negativ de mai jos a iesit si el 0), iar o cale de dosar inexistent fara bara nu e vazuta ca dosar
|
|
const ign = (n) => spawnSync('git', ['-C', RAD, 'check-ignore', '-q', '--no-index', rel(n) + '/raport.mjs'], { encoding: 'utf8', windowsHide: true }).status;
|
|
const r = [ign('.expunere-planta-x-1'), ign('.expunere-oglinda-x-1'), ign('expunere-alt-frate')];
|
|
// fara git sau in afara unui arbore git (o copie publicata a uneltei) intrebarea nu are raspuns: NEMASURAT numit, nu verde si nu rosu
|
|
if (r.some((x) => x !== 0 && x !== 1)) cer(et, 'NEMASURAT', `git check-ignore nu a raspuns (${r.join(',')}): git lipsa sau nu e un arbore git`);
|
|
else cer(et, da(r[0] === 0 && r[1] === 0 && r[2] === 1), `coduri check-ignore ${r.join(',')} (0 = ignorat)`);
|
|
}
|
|
await incearca('curatenia de la pornire: o copie-frate a unui proces MORT e stearsa, una a unui proces VIU (aceasta proba) e lasata, si se spune de ce', async () => {
|
|
const ch = spawnSync(process.execPath, ['-e', 'process.stdout.write(String(process.pid))'], { encoding: 'utf8', windowsHide: true, env: { ...process.env, NODE_OPTIONS: '' } });
|
|
const mort = Number(ch.stdout); if (!(mort > 0) || pidViu(mort)) return `nu s-a gasit un pid mort (${ch.stdout})`;
|
|
const par = path.join(AICI, '..'); const dMort = path.join(par, `.expunere-planta-curatenie-${mort}`), dViu = path.join(par, `.expunere-planta-curatenie-${process.pid}`);
|
|
fs.mkdirSync(dMort, { recursive: true }); fs.mkdirSync(dViu, { recursive: true }); copii.push(dMort, dViu);
|
|
const c = curataOrfane(par);
|
|
return [!fs.existsSync(dMort) && fs.existsSync(dViu) && c.sterse.includes(path.basename(dMort)) && c.lasate.some((x) => x.startsWith(path.basename(dViu)) && /traieste/.test(x)), `sterse ${c.sterse.length}, lasate ${c.lasate.length}`];
|
|
});
|
|
// dosarul de lucru din %TEMP%: poarta pid-ul rularii (numeT), deci curatenia de la pornire il recunoaste ca orfan cand procesul a murit;
|
|
// forma de dinainte (mkdtemp fara pid) nu poate fi judecata si ramane pe veci: PLANTAREA INVERSA o creeaza si cere ca ea sa ramana
|
|
await incearca('dosarul de lucru al probei (%TEMP%) poarta pid-ul: al unei rulari MOARTE e sters la pornire, al acesteia (VIE) e lasat; PLANTAREA INVERSA (numele de dinainte, fara pid) ramane dupa curatenie si iese ROSIE', async () => {
|
|
const ch = spawnSync(process.execPath, ['-e', 'process.stdout.write(String(process.pid))'], { encoding: 'utf8', windowsHide: true, env: { ...process.env, NODE_OPTIONS: '' } });
|
|
const mort = Number(ch.stdout); if (!(mort > 0) || pidViu(mort)) return `nu s-a gasit un pid mort (${ch.stdout})`;
|
|
const dMort = numeT(mort); const dVechi = fs.mkdtempSync(path.join(os.tmpdir(), 'expunere-proba-'));
|
|
try {
|
|
const eu = path.basename(T);
|
|
const c = curataOrfane(os.tmpdir(), TIPAR_T, pidDinT);
|
|
const bun = !fs.existsSync(dMort) && c.sterse.includes(path.basename(dMort)) && fs.existsSync(T) && pidDinT(eu) === process.pid;
|
|
const vechiulRamane = fs.existsSync(dVechi) && c.lasate.some((x) => x.startsWith(path.basename(dVechi)) && /fara pid/.test(x));
|
|
return [bun && vechiulRamane, `al rularii moarte sters: ${!fs.existsSync(dMort)}; al acesteia lasat: ${fs.existsSync(T)} (${eu.slice(0, 26)}); cel de forma veche lasat (nu poate fi judecat): ${vechiulRamane}`];
|
|
} finally { for (const d of [dMort, dVechi]) { try { fs.rmSync(d, { recursive: true, force: true }); } catch {} } }
|
|
});
|
|
// o macheta nu traieste mai mult decat procesul care a pornit-o: un parinte intermediar o porneste DETASAT (in afara jobului care pe
|
|
// Windows ii omoara pe copii odata cu parintele) si iese; paznicul de parinte trebuie sa o opreasca in cateva secunde
|
|
const machetaDupaParinte = async (fisierMachete, nume) => {
|
|
const gata = path.join(T, `gata-paznic-${nume}.json`), contor = path.join(T, `contor-paznic-${nume}.json`);
|
|
const argv = [fisierMachete, '--fel', 'electrum', '--fixtura', path.join(FIX, 'electrum.json'), '--gata', gata, '--contor', contor, '--plafon-s', '120'];
|
|
const cod = `const { spawn } = require('node:child_process'); const fs = require('node:fs'); const c = spawn(process.execPath, ${JSON.stringify(argv)}, { detached: true, stdio: 'ignore', windowsHide: true }); c.unref(); const t0 = Date.now(); const h = setInterval(() => { if (fs.existsSync(${JSON.stringify(gata)}) && fs.statSync(${JSON.stringify(gata)}).size > 0) { clearInterval(h); process.exit(0); } if (Date.now() - t0 > 10000) process.exit(3); }, 50);`;
|
|
const r = spawnSync(process.execPath, ['-e', cod], { encoding: 'utf8', timeout: 20000, windowsHide: true, env: { ...process.env, NODE_OPTIONS: '' } });
|
|
if (r.status !== 0 || !fs.existsSync(gata)) throw new Error(`parintele intermediar a iesit cu ${r.status}, fara macheta pornita`);
|
|
const { pid } = JSON.parse(fs.readFileSync(gata, 'utf8'));
|
|
for (let i = 0; i < 60 && pidViu(pid); i++) await sleep(100);
|
|
const ramasa = pidViu(pid); if (ramasa) { try { process.kill(pid); } catch {} }
|
|
return { ramasa, pid };
|
|
};
|
|
await incearca('machetele nu supravietuiesc parintelui: pornita DETASAT de un parinte care iese, macheta se opreste singura in 6 s (paznicul de parinte); PLANTAREA INVERSA (paznicul scos, intr-o copie) o lasa vie, si proba o opreste', async () => {
|
|
const bun = await machetaDupaParinte(MACHETE, 'bun');
|
|
if (bun.ramasa) return [false, 'macheta cu paznic e vie la 6 s dupa ce parintele a iesit (oprita de proba)'];
|
|
const d = copie('paznic'); const n = planteaza(path.join(d, 'machete.mjs'), 'setInterval(() => { if (!parinteViu()) process.exit(0); }, 1000).unref();', '');
|
|
if (n !== 1) return `tinta plantarii apare de ${n} ori`;
|
|
const plantat = await machetaDupaParinte(path.join(d, 'machete.mjs'), 'plantat');
|
|
return [!bun.ramasa && plantat.ramasa, `macheta cu paznic vie dupa 6 s: ${bun.ramasa}; fara paznic: ${plantat.ramasa} (oprita de proba)`];
|
|
});
|
|
// @noble/curves 2.x (fara ProjectivePoint si CURVE) intr-un node_modules gasit INAINTEA celui bun: incarcatorul il ocoleste; fara alt
|
|
// candidat, raportul se opreste cu cod 2 si numeste dependenta, nu crapa in import
|
|
await incearca('@noble/curves in forma 2.x gasit primul (un node_modules al copiei): incarcatorul il ocoleste si raportul merge pe 1.x (cod 0); fara alt candidat -> cod 2 "DEPENDENCY MISSING ... found without the exports"; PLANTAREA INVERSA (verificarea exporturilor scoasa) crapa in import (cod 1) si verificarea iese ROSIE', async () => {
|
|
const fals = (d) => { const p = path.join(d, 'node_modules', '@noble', 'curves'); fs.mkdirSync(p, { recursive: true }); fs.writeFileSync(path.join(p, 'package.json'), JSON.stringify({ name: '@noble/curves', version: '2.0.0-fals', type: 'module', exports: { './secp256k1.js': './secp256k1.js', './secp256k1': './secp256k1.js' } })); fs.writeFileSync(path.join(p, 'secp256k1.js'), 'export const secp256k1 = { Signature: class Signature {}, getPublicKey: () => new Uint8Array(33), Point: class Point {} };\n'); };
|
|
const fara1x = (d) => planteaza(path.join(d, 'noble.mjs'), "const dinAfara = (sub) => [process.env.AERE_EXPUNERE_NODE_MODULES, path.join(AICI, '..', '..', '..', 'verificator-falcon', 'node_modules')]", "const dinAfara = (sub) => (sub.startsWith('@noble/curves') ? ['nu-exista'] : [process.env.AERE_EXPUNERE_NODE_MODULES, path.join(AICI, '..', '..', '..', 'verificator-falcon', 'node_modules')])");
|
|
const d1 = copie('noble-2x'); fals(d1); const r1 = ruleaza({ dir: d1, args: ['--mod', '1', '--adrese', LISTA_BTC] });
|
|
const d2 = copie('noble-2x-singur'); fals(d2); if (fara1x(d2) !== 1) return 'tinta plantarii (noble.mjs) nu apare o data';
|
|
const r2 = ruleaza({ dir: d2, args: ['--mod', '1', '--adrese', LISTA_BTC] });
|
|
const numit0 = (r) => r.cod === 2 && !r.scris && /DEPENDENCY MISSING/.test(r.err) && /found without the exports/.test(r.err) && /@noble\/curves/.test(r.err);
|
|
if (!(r1.cod === 0 && r1.scris && numit0(r2))) return [false, `cu 1.x alaturi: cod ${r1.cod} | numai 2.x: cod ${r2.cod} ${r2.err.replace(/\s+/g, ' ').replace(/[A-Za-z]*Error/g, 'eroare').slice(0, 90)}`];
|
|
const d3 = copie('noble-2x-inv'); fals(d3); if (fara1x(d3) !== 1) return 'tinta plantarii (noble.mjs) nu apare o data';
|
|
if (planteaza(path.join(d3, 'noble.mjs'), "const curves = await incarca('@noble/curves/secp256k1.js', curbaPotrivita);", "const curves = await incarca('@noble/curves/secp256k1.js', (m) => !!m.secp256k1);") !== 1) return 'tinta plantarii inverse nu apare o data';
|
|
const r3 = ruleaza({ dir: d3, args: ['--mod', '1', '--adrese', LISTA_BTC] });
|
|
const numit = (r) => r.cod === 2 && !r.scris && /DEPENDENCY MISSING/.test(r.err) && /found without the exports/.test(r.err) && /@noble\/curves/.test(r.err) && !/TypeError/.test(r.err);
|
|
// inversa trebuie sa cada EXACT pe defectul plantat (o crapare in import, TypeError), nu pe o plantare care nu compileaza
|
|
// (clasa erorii nu se tipareste: oglinda probei citeste iesirea si un nume de eroare acolo ar arata ca o crapare a probei)
|
|
const eroare3 = (r3.err.split('\n').find((l) => /Error/.test(l)) || r3.err).trim().replace(/^[A-Za-z]*Error: /, '');
|
|
return [r1.cod === 0 && r1.scris && numit(r2) && !numit(r3) && r3.cod === 1 && /TypeError/.test(r3.err), `cu 1.x alaturi: cod ${r1.cod} | numai 2.x: cod ${r2.cod} ${r2.err.slice(0, 60)} | copia fara verificarea exporturilor: cod ${r3.cod}, crapare in import: ${eroare3.slice(0, 60)}`];
|
|
});
|
|
});
|
|
} catch (e) {
|
|
cer('proba a aruncat in afara sectiunilor', 'STRICAT', msg(e));
|
|
} finally {
|
|
for (const m of machete) { try { m.kill(); } catch {} }
|
|
for (const d of copii) { try { fs.rmSync(d, { recursive: true, force: true }); } catch {} }
|
|
try { fs.rmSync(T, { recursive: true, force: true }); } catch {}
|
|
}
|
|
console.log(`\nEXPUNERE-PROBA: verzi=${verzi} rosii=${rosii} stricate=${stricate} nemasurate=${nemasurate}`);
|
|
process.exitCode = rosii || stricate ? 1 : 0;
|