liberlume-content-v3 lang: it title: Bitcoin e quantum computing: contare da soli con un nodo summary: Il terzo pezzo della famiglia «Bitcoin e quantum computing»: non i numeri, ma il modo di rifarli. Tutti i comandi per contare da sé, con un nodo proprio, quante monete hanno la chiave pubblica già in vista: il censimento dell'UTXO set per tipo di serratura e per età, la passata che conta il riuso, l'indice degli outpoint, il controllo dei propri indirizzi senza darli a nessuno. Tre livelli, da minuti a giorni, che si possono lasciare a metà avendo comunque un risultato verificabile. Ogni passo porta l'output vero della nostra esecuzione al blocco 957.301 e le impronte su cui chiunque deve atterrare rifacendolo. Codice pubblico, licenza MIT. btc-anchor: 961334,0000000000000000000055dac03a1a980589be6db0b21b0a29e8c91ef5b61664 prev: genesis --- body --- # Bitcoin e quantum computing: contare da soli con un nodo ### Un caso tipico in cui chiunque, con un piccolo impegno, può applicare il «don't trust, verify» fino in fondo: nessun numero di questo manuale va creduto sulla parola, si può contare da sé. Il principio: ogni numero pubblicato qui deve essere riproducibile da chiunque abbia un nodo Bitcoin, senza fidarsi né dei report di terzi né di noi. È il «don't trust, verify» preso alla lettera, ed è la cosa che il lettore deve portarsi via prima ancora dei numeri: su Bitcoin ciò che è verificabile è a disposizione di tutti, e dove la verifica è possibile la fiducia semplicemente non serve. Gli strumenti di questo manuale sostituiscono la fiducia con la ripetizione: gli artefatti che costruiscono sono una funzione deterministica della catena — stesse altezze, stessi byte, stessa impronta, su qualunque macchina — e così due sconosciuti confrontano un'impronta invece di confrontare la fiducia. Questo manuale raccoglie i comandi esatti, il codice (pubblico e clonabile: [github.com/amenano/nodsig](https://github.com/amenano/nodsig)) e i casi pratici emersi facendolo davvero. Tutto è read-only: nessun wallet coinvolto, nessuna scrittura sulla catena. **Che cosa si ottiene, e a che prezzo.** Il manuale è in tre livelli, che rispondono a domande diverse e costano tempi molto diversi. Sono indipendenti, nel senso che ci si può fermare dopo il primo o dopo il secondo e avere comunque un risultato completo e verificabile. | | La domanda | Che cosa serve | Quanto costa | |---|---|---|---| | **Livello 1** | quante monete esistono adesso, quanti bitcoin, e qual è l'impronta che fa da ancoraggio a quel conteggio | un nodo raggiungibile | minuti | | **Livello 2** | come si distribuiscono per tipo di serratura e per età, e quante stanno per costruzione su chiavi già visibili | il nodo, più uno snapshot dell'UTXO set da contare su un'altra macchina | un pomeriggio | | **Livello 3** | quante monete stanno oggi dietro serrature già aperte in passato, cioè il riuso | una passata intera sulla storia della catena | giorni, non assistiti | Chi legge senza un nodo davanti può fermarsi qui e prendere il resto come il racconto di come si contano queste cose: i numeri, le impronte e i tempi reali sono nel testo e valgono come referto anche per chi non rifà i conti. Chi il nodo ce l'ha trova i comandi esatti, nella forma in cui li abbiamo eseguiti. I numeri che questa procedura ha prodotto, con la loro lettura, sono pubblicati a parte: [Bitcoin e quantum computing: i dati (luglio 2026)](/it/bitcoin-e-quantum-computing-dati-luglio-2026/). Qui si trova come ottenerli, lì che cosa dicono. Ambiente di riferimento delle esecuzioni riportate qui (dichiararlo rende onesti i tempi indicati): nodo completo non-pruned su Umbrel (Raspberry Pi, SSD esterno), app «Bitcoin Node» v1.1.0. I comandi valgono per qualunque Bitcoin Core; cambia solo il prefisso con cui si invoca `bitcoin-cli` (vedi sotto). Come si legge questo manuale: ogni comando è accompagnato in lingua piana da che cosa fa, che cosa restituisce, perché serve; i concetti minimi (UTXO set, altezza, hash, nodo pruned) si spiegano al primo uso. Chi sa già salta le spiegazioni; chi non sa segue tutto. Gli intoppi si riportano come casi d'esempio nostri, in coda al passo, senza farne il centro: il centro è che tutto questo è verificabile da sé. Le tappe. Le prime tre parti si leggono in fila: si allestisce, si costruisce, si chiede. La quarta è materiale di consultazione, e si apre quando serve. **[Prima di cominciare](#prima-di-cominciare)** - [Sei coppie che si confondono](#sei-coppie-che-si-confondono) - [Raggiungere bitcoin-cli](#raggiungere-bitcoin-cli) - [Privacy e dati sensibili](#privacy-e-dati-sensibili) - [Architettura: il nodo estrae, un'altra macchina elabora](#architettura-il-nodo-estrae-unaltra-macchina-elabora) - [Prerequisiti: accesso al nodo via RPC (tunnel e cookie)](#prerequisiti-accesso-al-nodo-via-rpc-tunnel-e-cookie) **[Costruire, in ordine](#costruire-in-ordine)** - [Livello 1 — i totali ancorati (minuti)](#livello-1-i-totali-ancorati-minuti) - [Livello 2 — la tabella per tipo e per età (un pomeriggio)](#livello-2-la-tabella-per-tipo-e-per-eta-un-pomeriggio) - [Livello 3 — il riuso (giorni, opzionale)](#livello-3-il-riuso-giorni-opzionale) - [Indice degli outpoint e derivati (`nodsig index`, `nodsig derived`)](#indice-degli-outpoint-e-derivati-nodsig-index-nodsig-derived) **[Chiedere](#chiedere)** - [Il tuo indirizzo è esposto?](#il-tuo-indirizzo-e-esposto) - [Il controllo degli indirizzi (`nodsig check`)](#il-controllo-degli-indirizzi-nodsig-check) - [Interrogare l'indice e i derivati](#interrogare-lindice-e-i-derivati) **[Il disegno e i formati](#il-disegno-e-i-formati)** - [Le tre idee del livello 3](#le-tre-idee-del-livello-3) - [Le due strade e la run senza assistenza](#le-due-strade-e-la-run-senza-assistenza) - [Lo schedario incrementale: appendibile per costruzione](#lo-schedario-incrementale-appendibile-per-costruzione) - [Il grafo co-emesso](#il-grafo-co-emesso) - [Che cosa rende questi file «di tutti»](#che-cosa-rende-questi-file-di-tutti) - [I principi dei formati](#i-principi-dei-formati) - [Gli artefatti: il flusso a colpo d'occhio](#gli-artefatti-il-flusso-a-colpo-docchio) - [Repo GitHub pubblico](#repo-github-pubblico) - [Registro delle esecuzioni](#registro-delle-esecuzioni) - [Riferimenti — gli algoritmi e le idee riprese](#riferimenti-gli-algoritmi-e-le-idee-riprese) ## Prima di cominciare ### Sei coppie che si confondono Prima dei comandi, sei distinzioni. Non sono pignolerie di lessico: ognuna, se salta, produce una conclusione sbagliata che si vede circolare spesso. **Rivelazione ≠ riuso.** Una chiave pubblica si rivela sulla catena nel momento in cui si spende: è la norma, succede a chiunque, e di per sé non lascia niente in pericolo. Il riuso è la moneta che resta, o che torna, dietro una serratura la cui chiave era già stata rivelata. Il primo è un evento, il secondo è uno stato. I numeri di questo manuale contano il secondo. **Esposto ≠ vulnerabile.** Esposto vuol dire che la chiave pubblica è leggibile sulla catena. Vulnerabile vorrebbe dire che qualcuno può usarla per prendere quelle monete, e servirebbe una macchina che oggi non esiste. Confondere i due termini trasforma una misura in un allarme. **Serratura ≠ portafoglio.** Una serratura è uno scriptPubKey identico a sé stesso. Un portafoglio è un insieme di chiavi in mano a qualcuno. Contare serrature non è contare persone: una sola entità può starne dietro a milioni, e un solo portafoglio può usarne una sola per anni. **Chiave ≠ indirizzo.** L'indirizzo è una codifica leggibile di una serratura. La stessa chiave può presentarsi dietro indirizzi di forma diversa, le sue «facce», e resta la stessa chiave. È il motivo per cui il perimetro `faces` esiste, ed è il motivo per cui va dichiarato ogni volta. **UTXO set ≠ storia.** Lo UTXO set è la fotografia di ciò che esiste adesso, le monete non spese. La storia è tutto quello che è successo, comprese le monete già spese e i loro conti chiusi. Una domanda sul presente e una sul passato hanno risposte diverse, e questo manuale le tiene su due livelli separati apposta. **Impronta ≠ firma.** Un'impronta dice che due file sono lo stesso file, e chiunque la ricalcola per confrontare. Una firma dice chi ha prodotto qualcosa. Qui si usano impronte: servono a rendere possibile il confronto tra estranei che non hanno alcun motivo di fidarsi l'uno dell'altro. ### Raggiungere bitcoin-cli Tutti i comandi del manuale sono comandi di `bitcoin-cli`, l'interfaccia a riga di comando di Bitcoin Core. Cambia solo *come* lo si invoca a seconda dell'installazione; nel resto del manuale i comandi sono scritti nella forma generica, con la variante Umbrel indicata dove serve. **Installazione classica su PC** (Bitcoin Core installato direttamente, il demone in esecuzione): `bitcoin-cli` si invoca e basta, trova da solo il cookie RPC nel datadir di default (`~/.bitcoin` su Linux): ```bash bitcoin-cli ``` Se il datadir è in una posizione non standard: `bitcoin-cli -datadir= `. **Umbrel**: il demone gira in un container Docker, e con l'app Bitcoin nuova (v1.x, rewrite 2025) **non esiste più un container `bitcoind` separato**: il demone vive dentro `bitcoin_app_1`, insieme all'interfaccia. Quindi: ```bash ssh umbrel@umbrel.local # password: quella della dashboard # per orientarsi: la lista dei container col nome e l'immagine sudo docker ps --format '{{.Names}}\t{{.Image}}' # → cercare l'immagine ghcr.io/getumbrel/umbrel-bitcoin → nome bitcoin_app_1 # ogni bitcoin-cli del manuale diventa: sudo docker exec bitcoin_app_1 bitcoin-cli ``` Verificato sul campo: `bitcoin-cli` dentro `bitcoin_app_1` trova da solo il cookie RPC, nessun `-datadir` né credenziali necessari. Caso d'esempio, dal nostro setup: filtrando `docker ps` per «bitcoin» saltano fuori anche i container di contorno (`bitcoin_tor_1`, `bitcoin_i2pd_daemon_1`, `bitcoin_app_proxy_1`): sono Tor, I2P e il proxy web, non il nodo. Altre installazioni a pacchetto (Start9, RaspiBlitz, MyNode…) hanno ciascuna il proprio modo di esporre `bitcoin-cli`; il principio non cambia: individuare dove gira il demone e premettere quel che serve al comando. ### Privacy e dati sensibili Prima dei comandi, il perimetro dei dati: che cosa di ciò che questi passi producono si può condividere, e che cosa no. **Due classi di dati, da tenere separate sempre:** - *Condivisibili senza pensarci* — dati pubblici della catena o aggregati: altezze, hash di blocco, muhash, totali (txouts, BTC), tabelle per tipo/età, output di `getblockchaininfo` e `gettxoutsetinfo`, nomi di container. Non dicono nulla di chi li ha prodotti; - *SENSIBILI — mai in chat, mai su forum, mai a servizi terzi*: i PROPRI indirizzi e tutto ciò che li collega a te; il file-lista di input del controllo (è la mappa del tuo portafoglio); il suo OUTPUT (indirizzi + saldi + risposte: ancora peggio); xpub; credenziali RPC; il seed, ovvio, MAI da nessuna parte in nessuna forma. Due trappole di comando da conoscere, perché la seconda classe sfugge volentieri per vie laterali: `docker inspect ` e `printenv` dentro il container ESPONGONO le variabili d'ambiente, credenziali RPC incluse; e anche `bitcoin.conf` le contiene. Non incollarne mai l'output grezzo, da nessuna parte — nemmeno chiedendo aiuto. **Regole di disegno degli script (verificabili leggendo il codice):** - parlano SOLO col nodo proprio: nessuna chiamata a endpoint esterni, mai — chi legge il codice deve poterlo constatare in un minuto; - il controllo scrive l'esito su FILE locale (non a video di default: lo schermo finisce negli screenshot, nelle condivisioni, nelle sessioni remote) e stampa in testa un avviso: «questo file descrive il tuo portafoglio: trattalo come tale»; - i file di input/output del controllo vanno trattati come metadati del wallet: cancellarli dopo l'uso o conservarli cifrati; - gli output aggregati (livelli 1-2) sono condivisibili per costruzione: è un CRITERIO di disegno, non un caso (il riepilogo non contiene mai singoli indirizzi). **Regola di questo manuale:** se un passo produce dati della seconda classe, lo si dice PRIMA del comando — con l'avvertenza sul leak classico, che è chiedere aiuto su un forum (o a un'AI) incollando l'output con dentro i propri indirizzi. ### Architettura: il nodo estrae, un'altra macchina elabora Scelta di fondo: sul nodo girano SOLO i comandi `bitcoin-cli` (sola lettura) e la produzione degli snapshot; tutta l'elaborazione nostra (parser, analisi, tabelle) gira su un'altra macchina, dove c'è CPU e agilità di sviluppo. Motivi: il nodo resta pulito (nessuna dipendenza installata, nessun rischio: fa girare anche Lightning e altri servizi); su un Pi il parsing di ~166M voci in Python costerebbe ore contro i minuti di un PC; il trasferimento del file (~12 GB via `scp` su rete locale, ~5-10 minuti, una tantum) è il falso problema. Per il lettore non cambia nulla di concettuale: gli script prendono in input un file e girano OVUNQUE ci sia Python — chi ha il nodo su un PC li lancia lì, chi ha un Pi li lancia dove preferisce. La fiducia non si sposta (sono comunque le proprie macchine) e l'aggancio di verifica resta la coppia altezza+muhash calcolata dal nodo. Eccezioni coerenti col principio: il controllo degli indirizzi, per il saldo e per quello soltanto, dialoga col nodo VIVO e non con un file, quindi gira dal PC via tunnel SSH e solo se glielo si chiede con `--rpc`; il livello 3 non copia i ~900 GB di blocchi ma li LEGGE dalla rete locale (via RPC, o via l'interfaccia REST del nodo se attivata: vedi i Prerequisiti), macinando sul PC. ### Prerequisiti: accesso al nodo via RPC (tunnel e cookie) I livelli 2 e 3 elaborano su una macchina diversa dal nodo (vedi «Architettura»): servono un canale verso l'RPC del nodo e le sue credenziali. Si allestisce una volta, prima dei passi che interrogano il nodo (il livello 1 usa solo `bitcoin-cli` sul nodo e non ne ha bisogno). **Accesso al nodo, senza toccarne la configurazione.** Niente modifiche a `rpcbind`/`rpcallowip`: la configurazione del nodo non si tocca. Verificato sul pilota: il container `bitcoin_app_1` pubblica l'RPC sulla porta 8332 dell'host, e Docker la pubblica su `0.0.0.0` — cioè l'RPC di un nodo Umbrel è già raggiungibile dalla LAN così com'è, per costruzione (serve alle altre app). Questo NON rende superfluo il tunnel, al contrario: l'autenticazione JSON-RPC è HTTP Basic, cioè viaggia in chiaro, e senza tunnel il cookie attraverserebbe la LAN in chiaro a ogni richiesta. Quindi: `ssh -N -L 8332:127.0.0.1:8332 umbrel@` — il PC vede l'RPC su `127.0.0.1:8332` e le credenziali restano dentro il canale cifrato. Le credenziali: il cookie RPC (utente `__cookie__`), letto con `docker exec` dal datadir del container e salvato in un file locale con permessi `600`, MAI mostrato a video; pulizia di fine sessione: il file si cancella (e il tunnel si chiude). Il cookie ruota a ogni riavvio di bitcoind — che è anche il modo di REVOCARLO se serve — e lo script lo dice chiaro se l'autenticazione fallisce. Digitare password (SSH/sudo) è un passo che resta nel terminale dell'utente, fuori da ogni sessione assistita, come per la firma. *Come si copia il cookie, in pratica.* `sudo` sul nodo chiede la password in interattivo, e digitarla mentre si cattura lo stdout in un file (via pipe o `ssh -t`) non funziona: il prompt finisce nell'output o la sessione si impianta. Si separano quindi le due cose — il `sudo` in una sessione SSH interattiva vera, che scrive il cookie in un file temporaneo dell'utente sul nodo, poi lo si copia via `scp`. Passo 1, sul nodo (la password sudo si digita qui, nel terminale dell'utente): ssh umbrel@ (umask 077; sudo docker exec bitcoin_app_1 cat /.cookie > ~/cookie.tmp) exit Il redirect `>` lo fa la shell dell'utente sul nodo, quindi `~/cookie.tmp` è suo e nasce `600` (umask 077); `sudo` eleva solo il `docker exec`, il cookie non compare a video. Passo 2, dal PC — copia ed elimina il temporaneo: mkdir -p ~/utxo_work (umask 077; scp umbrel@:~/cookie.tmp ~/utxo_work/.btc_cookie) ssh umbrel@ 'shred -u ~/cookie.tmp' chmod 600 ~/utxo_work/.btc_cookie Il datadir è quello del bitcoind nel container (su Umbrel di norma `/data/.bitcoin`), il container quello del proprio nodo (qui `bitcoin_app_1`, verificato sul posto); l'utente SSH del nodo di norma non è nel gruppo `docker`, da cui il `sudo`. Il segreto resta un istante su `~/cookie.tmp` (600), poi `shred -u`; in locale è `600`. Verifica SENZA stamparlo — formato giusto e nessun messaggio d'errore catturato per sbaglio: wc -c ~/utxo_work/.btc_cookie # ~70+ byte grep -q '^__cookie__:' ~/utxo_work/.btc_cookie && echo OK || echo INVALIDO Il prefisso `__cookie__:` non è segreto (è sempre quello). Poi si passa `--cookie-file ~/utxo_work/.btc_cookie` agli strumenti: nessun flag accetta un segreto sulla riga di comando, per disegno — l'argv di un processo è leggibile da chiunque sulla macchina, per tutta la durata dell'esecuzione (l'alternativa, per un nodo con credenziali fisse, è la variabile d'ambiente `NODSIG_RPC_AUTH`). A fine sessione `shred -u` sul file locale e tunnel giù. Il cookie ruota a ogni riavvio di bitcoind: se l'autenticazione fallisce, si rifà. **Un filo più economico, se il nodo lo offre (`--rest`).** L'interfaccia REST di Bitcoin Core (`rest=1` in `bitcoin.conf`, servita sulla stessa porta dell'RPC) consegna i blocchi in binario invece che in esadecimale dentro JSON: circa metà dei byte sul filo, per l'unico passo il cui costo È il filo. E non autentica nessuno, quindi il fetch non porta credenziali: il cookie serve solo alle chiamate RPC che restano. Il trasporto si sceglie per i byte, mai per la fiducia: i controlli di integrità non cambiano di una virgola — ogni byte è verificato per hash comunque, vedi «Lo schedario incrementale» — e le due strade sono collaudate a produrre artefatti byte-identici. REST non ha batching, due richieste per blocco, quindi si accompagna a `--prefetch-depth ` per tenere più richieste in volo. Attraverso il tunnel SSH non cambia nulla: stessa porta, stesso canale cifrato. ## Costruire, in ordine ### Livello 1 — i totali ancorati (minuti) Il concetto da avere in mano prima dei comandi, e qui vale una definizione estesa perché è l'oggetto su cui lavora l'intero manuale: l'**UTXO set** (*Unspent Transaction Output set*: l'insieme delle uscite di transazione non spese). Bitcoin non ha conti né saldi. Il registro non dice mai «l'indirizzo X possiede Y bitcoin»: registra soltanto transazioni. Ogni transazione consuma per intero alcune monete esistenti (le uscite, *output*, di transazioni precedenti) e ne conia di nuove; ogni uscita nuova è una moneta, con un importo qualsiasi e una serratura (lo script) che dichiara che cosa serve per spenderla. Più contanti che conto corrente: monete di taglio arbitrario, ciascuna col suo lucchetto, e spendere significa fondere le monete scelte e coniarne di nuove (compreso il resto, che è una moneta nuova verso sé stessi). Il «saldo» che mostra un wallet è una finzione comoda: la somma delle monete che le sue chiavi sanno aprire. L'UTXO set è l'insieme di tutte le monete non ancora consumate, in questo istante: lo stato presente del sistema, distillato dall'intera storia. Chi riparte dal blocco genesi e rigioca ogni transazione arriva esattamente a questo insieme; ed è l'insieme che ogni nodo mantiene per validare, perché una transazione è valida solo se consuma monete che ci stanno dentro (è così che la doppia spesa muore: la moneta consumata esce dal set, e una transazione che provasse a rispenderla non troverebbe nulla). A parità di altezza di blocco, l'insieme è IDENTICO per tutti i nodi onesti del mondo: per questo un'impronta calcolata su di esso si può confrontare tra sconosciuti. Ogni voce del set porta con sé quattro cose: l'importo, la serratura, il flag «è una ricompensa di mining?» e l'altezza del blocco in cui è nata. Le ultime due sono un regalo per noi: permettono il censimento per tipo E per età senza leggere la storia. È l'insieme su cui faremo tutti i conteggi: chi vuole sapere quante monete hanno la chiave esposta deve contare lì dentro. Il livello 1 stabilisce i totali di controllo: quante voci, quanti bitcoin in tutto, e un'impronta dell'insieme che permette a chiunque di verificare che stiamo contando la stessa cosa. #### 1a. Fotografia del nodo - **Obiettivo:** confermare che il nodo è completo e al passo con la rete, e fissare l'altezza di partenza. - **Serve a:** l'altezza è la «data» a cui viaggia ogni numero del manuale; è il presupposto di tutti i conteggi. - **Richiede:** un nodo Bitcoin Core sincronizzato e non potato (vedi «Raggiungere bitcoin-cli»). - **Si compone di:** `bitcoin-cli getblockchaininfo`. ```bash bitcoin-cli getblockchaininfo ``` (su Umbrel: `sudo docker exec bitcoin_app_1 bitcoin-cli getblockchaininfo`) *Che cosa fa:* chiede al proprio nodo a che punto è della catena. *Che cosa restituisce:* un blocchetto JSON con lo stato del nodo. *Perché ci serve:* prova che si sta contando su un nodo completo e al passo con la rete, e fissa il punto di partenza (l'altezza). Da controllare nell'output, voce per voce: - `"chain": "main"` — si sta guardando la rete Bitcoin vera, non una di test; - `"blocks"` = `"headers"` e `"initialblockdownload": false` — il nodo ha scaricato e verificato tutta la catena, non sta ancora recuperando; - `"pruned": false` — nodo *non potato*: conserva tutti i blocchi dal 2009, non solo gli ultimi (serve per le analisi storiche dei livelli successivi). Nota che vale il viaggio: per validare il PROSSIMO blocco la storia non serve, basta lo stato corrente (un nodo potato valida con lo stesso rigore: ha verificato tutta la storia una volta, poi la scarta). La storia serve ai NUOVI ARRIVATI: chi accende un nodo oggi può verificare da zero solo se qualcuno conserva i blocchi e glieli serve — se nessuno li tenesse, si dovrebbe accettare uno stato confezionato da altri, sulla fiducia. Per questo la storia vive in molte copie indipendenti e non in un archivio ufficiale (che sarebbe di nuovo un arbitro). Tre strati, tre gradi di necessità: il consenso ha bisogno solo dello stato; la verificabilità da parte di chiunque ha bisogno della storia; la comodità ha bisogno degli indici (vedi la nota nel controllo degli indirizzi); - `"blocks"` è l'**altezza**: il numero progressivo dell'ultimo blocco. È la «data» di ogni misura on-chain: ogni numero che pubblichiamo viaggia con l'altezza a cui è stato contato. Nostra esecuzione, al blocco 957.281: - altezza: **957.281**; bestblock `000000000000000000015137f080f2ca0fa0be807f32ca206aac0f810805dfa9` - `pruned: false`, catena su disco ~858 GB, nessun warning #### 1b. Il conteggio dell'UTXO set - **Obiettivo:** i totali di controllo dell'UTXO set (numero di voci, bitcoin totali) e la sua impronta `muhash`. - **Serve a:** è la cornice di tutti i numeri successivi; l'impronta permette a un terzo di verificare che sta contando lo stesso insieme. - **Richiede:** il nodo del passo 1a. - **Si compone di:** `bitcoin-cli gettxoutsetinfo muhash`. ```bash bitcoin-cli -rpcclienttimeout=0 gettxoutsetinfo muhash ``` (su Umbrel: `sudo docker exec bitcoin_app_1 bitcoin-cli -rpcclienttimeout=0 gettxoutsetinfo muhash`) *Che cosa fa:* passa in rassegna l'intero UTXO set, voce per voce, e nel frattempo ne calcola un'impronta complessiva, il **muhash**. Merita la sua definizione, perché è l'aggancio di verifica di tutto il manuale: è un'impronta pensata per gli *insiemi*. Ogni voce viene ridotta con un hash a un numero enorme, e i numeri delle voci si combinano tra loro con una moltiplicazione: siccome l'ordine dei fattori non cambia il prodotto, l'impronta dipende solo da *che cosa* sta nell'insieme, non dall'ordine in cui lo si visita; e siccome aggiungere o togliere una voce equivale a moltiplicare o dividere, il nodo può tenerla aggiornata blocco dopo blocco senza mai rifare il conto da zero. Due insiemi uguali danno lo stesso muhash; basta una voce diversa e l'impronta cambia completamente, senza alcuna relazione riconoscibile con quella di prima. *Che cosa restituisce:* il totale delle voci (`txouts`), il totale dei bitcoin spendibili (`total_amount`), l'altezza a cui ha contato (`height`) e l'impronta (`muhash`). *Perché ci serve:* è la cornice di tutti i numeri successivi. L'impronta è il punto: due nodi onesti alla stessa altezza ottengono lo **stesso** `muhash`; chi rifà i nostri conti può prima verificare di stare contando sullo stesso insieme, poi confrontare i risultati. Avvertenze: - scansiona ~166 milioni di voci: il comando *sembra* non fare nulla e poi risponde tutto insieme. Su un PC recente sono minuti; su un Raspberry Pi la CPU è il collo di bottiglia e possono volerci 30-90 MINUTI (verificato sul campo). Per controllare che stia lavorando, da un'altra finestra: `sudo docker stats bitcoin_app_1 --no-stream` → CPU% ~100 = sta calcolando. È di sola lettura, il nodo continua a lavorare normalmente; - su hardware lento conviene lanciarlo dentro `tmux` (o con `nohup … > risultato.json &`): se la sessione SSH cade, il nodo finisce il calcolo ma la risposta non ha più nessuno a cui arrivare. NON rilanciare il comando in parallelo «per sicurezza»: partirebbe una seconda scansione completa accanto alla prima; - `-rpcclienttimeout=0` dice al client di aspettare senza limite di tempo: il default (15 minuti) su hardware lento può scadere prima della risposta; - il conteggio avviene all'altezza che il nodo ha in quel momento (non necessariamente quella del passo 1a — la rete non ci aspetta): **altezza e `muhash` vanno sempre pubblicati insieme**, sono la coppia che rende il conteggio confrontabile. A un'altezza diversa, `muhash` diverso ma totali confrontabili. Campi da riportare: `height`, `bestblock`, `txouts`, `total_amount`, `muhash`. Nostra esecuzione, al blocco 957.282 (durata ~1h+ su Pi: la catena è avanzata di 7+ blocchi durante il calcolo): ```json { "height": 957282, "bestblock": "000000000000000000011e8f11c5e0333ed259047f7c4db5fe84805de0243000", "txouts": 166238015, "muhash": "38dd237f9f83e14d2e8fefa396871a29c9a6226fc7f1f89eda2d9279e314671b", "total_amount": 20053779.26088761, "transactions": 114967902, "disk_size": 11478352648 } ``` Verifica di coerenza, ed è il punto didattico: all'altezza 957.282 l'emissione teorica massima è ~20.054.009 BTC (10,5M + 5,25M + 2,625M + 1,3125M delle prime quattro epoche di sussidio + 117.283 blocchi × 3,125). Il nodo conta ~230 BTC in meno, ed è GIUSTO: mancano il coinbase del genesi (50 BTC, non spendibile per costruzione) e le ricompense reclamate incomplete da alcuni miner negli anni. Il conteggio indipendente è coerente col protocollo al centesimo. Note: il conteggio è all'altezza in cui il comando È PARTITO (957.282, un blocco dopo la fotografia 1a); `disk_size` ~11,5 GB conferma la taglia prevista del trasferimento al livello 2. ### Livello 2 — la tabella per tipo e per età (un pomeriggio) Gli strumenti sono pronti e collaudati; l'esecuzione sta sul nodo (passi 2a-2b), poi 2c sulla macchina di lavoro. Flusso a due macchine, vedi «Architettura»: il nodo scatta la fotografia, un'altra macchina la conta. Il concetto prima dei comandi: il livello 1 ha *misurato* l'UTXO set (quante voci, quanti bitcoin, un'impronta); il livello 2 lo *scrive su un file* — lo snapshot — e lo conta voce per voce: per ogni moneta, che tipo di serratura la chiude e a che altezza di blocco è nata (ogni voce dell'UTXO set porta con sé l'altezza di creazione: è ciò che permette la distribuzione per età senza leggere la storia). Ne escono la tabella per tipo, il limite inferiore certo delle monete a chiave esposta (P2PK + P2TR + multisig bare) e la risposta a «quanto è fermo dall'era delle origini?». #### 2a. Lo snapshot sul nodo - **Obiettivo:** scrivere l'UTXO set su file (`utxos.dat`) a un'altezza fissa, con la sua impronta di contenuto `txoutset_hash`. - **Serve a:** è la materia prima del censimento (2c) e del `prepare` del livello 3; fissa l'altezza a cui vale tutto. - **Richiede:** nodo con Bitcoin Core ≥ 28 e spazio su disco per ~12 GB. - **Si compone di:** `bitcoin-cli dumptxoutset`. Prerequisito: Bitcoin Core **28 o successivo** (lo snapshot in formato v2). Per controllare la versione: ```bash bitcoin-cli -version ``` (su Umbrel: `sudo docker exec bitcoin_app_1 bitcoin-cli -version`) Poi lo snapshot vero e proprio: ```bash bitcoin-cli -rpcclienttimeout=0 dumptxoutset utxos.dat latest ``` (su Umbrel: `sudo docker exec bitcoin_app_1 bitcoin-cli -rpcclienttimeout=0 dumptxoutset utxos.dat latest`) *Che cosa fa:* passa in rassegna l'intero UTXO set (la stessa scansione del livello 1) e lo scrive compresso in un file, `utxos.dat`. *Che cosa restituisce:* un JSON con `coins_written` (quante voci ha scritto), `base_height` e `base_hash` (l'altezza e il blocco della fotografia), `path` (dove ha scritto il file) e `txoutset_hash` (un'impronta del CONTENUTO dello snapshot). *Perché ci serve:* è la materia prima del censimento; tutti i conteggi del livello 2 valgono all'altezza `base_height`. Avvertenze: - il percorso relativo (`utxos.dat` e basta) finisce DENTRO il datadir del nodo: sul nostro ambiente è la scelta giusta, perché il datadir sta sull'SSD — mai dare percorsi assoluti sulla SD di sistema (19 GB: il file da ~12 GB la riempirebbe). Prima di partire, controllare lo spazio: `df -h /mnt/data`; - stessi tempi e stesse cautele del livello 1, più la scrittura dei ~12 GB: su un Pi aspettarsi 1-2 ORE; lanciarlo dentro `tmux`, non rilanciarlo in parallelo, e nel frattempo evitare altri comandi RPC pesanti; - da REGISTRARE (vanno nel registro in fondo): `base_height`, `base_hash`, `coins_written`, `txoutset_hash`. Nostra esecuzione, al blocco 957.301 (inizio luglio 2026; in tmux sul Pi): ```json { "coins_written": 166224061, "base_hash": "000000000000000000005f3f0d5434e16bb6ea806ecd140be91bf6a15562c077", "base_height": 957301, "path": "/data/bitcoin/utxos.dat", "txoutset_hash": "ec79aed99b8d22d7315c76488fe09975ef9258039ee5583c7e8c7b6dc8d4cc58", "nchaintx": 1393498474 } ``` Osservazione da riusare nel testo: 166.224.061 voci a 957.301 contro le 166.238.015 del livello 1 a 957.282 — ~14.000 voci in MENO in 19 blocchi. L'UTXO set può anche restringersi: succede quando le transazioni consumano più monete di quante ne creino (consolidamenti). I due numeri non devono coincidere, devono essere VICINI; ed è un buon esempio del perché ogni conteggio viaggia con la sua altezza. (`nchaintx` = transazioni totali della storia fino a quel blocco, non ci serve qui.) Il punto di verifica, ed è il gemello del muhash del livello 1: chiunque abbia un nodo con Core ≥ 28 può rifare lo snapshot ALLA NOSTRA STESSA ALTEZZA anche dopo che la catena è andata avanti, con la variante `rollback` (`dumptxoutset utxos.dat rollback=`: il nodo riavvolge temporaneamente il proprio stato fino a quell'altezza — operazione legittima ma pesante, da fare su una macchina con margine, non è richiesta per seguire il manuale). Stessa altezza → stesso contenuto → stesso `txoutset_hash`: i nostri numeri sono riproducibili ESATTAMENTE, non «circa». Chi invece scatta lo snapshot alla propria altezza corrente otterrà totali leggermente diversi (la catena nel frattempo è cresciuta) ma le stesse proporzioni: entrambe le verifiche sono oneste, dicono cose diverse. #### 2b. Il trasferimento sulla macchina di lavoro - **Obiettivo:** portare `utxos.dat` dal nodo alla macchina che elabora, con l'integrità verificata da un'impronta identica ai due capi. - **Serve a:** il censimento gira dove c'è CPU, non sul nodo (architettura a due macchine). - **Richiede:** lo snapshot del 2a e l'accesso SSH al nodo. - **Si compone di:** `docker cp` / `scp` per il trasporto, `sha256sum` per il controllo. Il file va portato dove c'è CPU (vedi «Architettura»). Sul nostro ambiente sta dentro il container Docker, e la strada verificata sul campo è in due tempi: prima una sessione SSH interattiva sul nodo, per portare il file fuori dal container in un punto leggibile dal proprio utente, sull'SSD: ```bash ssh umbrel@umbrel.local # dentro la sessione: sudo docker cp bitcoin_app_1:/data/bitcoin/utxos.dat /mnt/data/utxos.dat sudo chown umbrel /mnt/data/utxos.dat sha256sum /mnt/data/utxos.dat # impronta lato nodo, già che si è lì exit ``` *Che cosa fa:* copia il file dal container al disco del nodo e lo intesta al proprio utente, così il passo successivo non ha bisogno di permessi. *Perché in sessione interattiva:* `sudo` deve poter chiedere la password. Poi, dalla macchina di lavoro, un normale `scp`: ```bash scp umbrel@umbrel.local:/mnt/data/utxos.dat . ``` (installazione classica su PC: solo l'`scp`, o niente del tutto se nodo e macchina di lavoro coincidono) Tempi: rete locale, ~9,6 GB → dai 5 ai 20 minuti a seconda di rete e disco; anche il `docker cp` e lo `sha256sum` sul Pi macinano qualche minuto in silenzio, è normale. Il punto d'arrivo è uno solo, comunque ci si arrivi col proprio setup: il file sulla macchina di lavoro con l'impronta che coincide. Casi d'esempio dal nostro percorso, se qualcosa si mette di traverso: - il comando «tutto in uno» (`ssh nodo "sudo docker exec … cat …" > file`) è più diretto ma richiede sudo senza password; altrimenti si ferma con `sudo: a terminal is required to read the password`, ed è il motivo del giro in due tempi qui sopra. Da sapere: aggirarlo con `ssh -t` non va bene per i file binari (il terminale virtuale altera i byte in transito); - `Host key verification failed` alla prima connessione da una macchina nuova è SSH che chiede di riconoscere il nodo, non un problema di password: una connessione interattiva di prova, `yes` al fingerprint, e si procede (senza disabilitare il controllo con `StrictHostKeyChecking=no`); - su Windows l'impronta si calcola con `Get-FileHash utxos.dat -Algorithm SHA256` (PowerShell; esce in maiuscolo, il confronto ignora maiuscole/minuscole). Verifica di integrità del trasferimento, su entrambe le macchine: ```bash # sul nodo: sudo docker exec bitcoin_app_1 sha256sum # sulla macchina di lavoro: sha256sum utxos.dat ``` *Che cosa fa:* calcola l'impronta SHA-256 del file, di qua e di là. *Perché ci serve:* se anche un solo byte si fosse corrotto nel viaggio, le due impronte sarebbero diverse (e il censimento fallirebbe in modi strani). Impronte uguali = copia identica, si procede. Privacy: lo snapshot è l'UTXO set pubblico, identico per tutti i nodi — prima classe, condivisibile. A trasferimento verificato, sul nodo restano DUE copie da cancellare (quella nel container e quella del `docker cp`): ```bash sudo docker exec bitcoin_app_1 rm /data/bitcoin/utxos.dat sudo rm /mnt/data/utxos.dat ``` La copia sulla macchina di lavoro conviene invece TENERLA: è un artefatto ancorato e riproducibile (stessa altezza → stesso `txoutset_hash`), riusabile per altre analisi alla stessa altezza senza rifare il dump. #### 2c. Il censimento (`nodsig census`) - **Obiettivo:** dalla `utxos.dat`, la tabella per tipo di serratura, il pavimento certo delle monete a chiave esposta, la distribuzione per epoca e un CSV degli aggregati. - **Serve a:** sono i numeri pubblicati del livello 2; il pavimento entra nel totale dell'esposizione. - **Richiede:** `utxos.dat` sulla macchina di lavoro (2b). - **Si compone di:** `nodsig census`, col suo collaudo nella batteria. Gli strumenti stanno nel repo pubblico `nodsig` ([github.com/amenano/nodsig](https://github.com/amenano/nodsig), licenza MIT; vedi «Repo GitHub pubblico» in coda). Servono Python 3.10 o successivo e nient'altro: nessuna dipendenza, la libreria standard è tutto il runtime. Ogni comando del manuale ha la forma `nodsig …`; da un clone, senza installare niente, lo stesso programma si invoca con `python3 -m nodsig …`, e il singolo modulo con `python3 -m nodsig.`, che è il modo di leggere il codice mentre gira. - **il censimento** (`nodsig census`): legge lo snapshot in streaming (mai tutto in memoria: gira anche su macchine modeste) e produce la tabella per tipo, il limite inferiore certo delle monete a chiave esposta, la distribuzione per epoca di sussidio dei tipi esposti e un CSV con i totali per tipo e fascia di 50.000 blocchi. SOLO aggregati: nell'output non compare mai un singolo indirizzo — condivisibile per costruzione; - **il suo collaudo**: costruisce uno snapshot sintetico di monete inventate (scrivendo il formato con un'implementazione indipendente, speculare al lettore) e verifica ogni numero del censimento, compreso il caso insidioso di una moneta a cavallo del confine di epoca (altezza 215.000: fascia che inizia in epoca 1, moneta in epoca 2). Eseguito: PASS. ```bash python3 -m pytest -k census -q # il collaudo del censimento, senza dati reali nodsig census utxos.dat --csv census.csv ``` (Il collaudo è l'unica cosa in tutto il repo che chieda una dipendenza esterna, `pytest`. Gli strumenti no: quelli girano con la sola libreria standard, ed è una proprietà che si controlla leggendo gli import.) *Che cosa fa:* decodifica il formato dello snapshot (è lo stesso letto dal convertitore di riferimento pubblicato da Bitcoin Core in `contrib/utxo-tools/utxo_to_sqlite.py`, da cui i dettagli sono stati verificati alla fonte) e classifica ogni voce. *Che cosa restituisce:* a video il riepilogo; su file il CSV degli aggregati. In testa stampa l'hash del blocco scritto nello snapshot: DEVE coincidere col `base_hash` del passo 2a, e le voci lette con `coins_written` — è il filo che lega il censimento alla fotografia. *Perché ci serve:* è la tabella per tipo e per età, coi nostri numeri. Tempi misurati (benchmark su snapshot sintetico da 2M voci): ~10 secondi per 2 milioni di voci su un PC recente → ~15 minuti per i ~166M reali. Python, solo libreria standard, nessuna dipendenza. Verifiche incrociate: - totali del censimento vs livello 1 (`txouts`, `total_amount`): alla stessa altezza devono coincidere ESATTAMENTE; ad altezze vicine, quasi; - stesso snapshot dato in pasto a `utxo_to_sqlite.py` di Bitcoin Core → qualche query SQL sui totali → devono coincidere col nostro CSV; - eventualmente `bitcoin-utxo-dump` (Go, legge il chainstate per conto suo) come terza campana. Nostra esecuzione, al blocco 957.301: - trasferimento: file da 8,9 GiB (~9,6 GB), sha256 verificato su entrambe le sponde. Nota: la stima «~12 GB» veniva dal `disk_size` del livello 1, che misura il chainstate su disco (LevelDB), non il dump compatto: ~57 byte a voce; - censimento sul PC (WSL, file letto da disco Windows): **10 minuti** per 166.224.061 voci. Controlli superati: hash del blocco in testa allo snapshot = `base_hash` del dump (`…5562c077`); voci lette = coins_written, file consumato fino all'ultimo byte; - **la verifica incrociata**: totale del censimento 20.053.838,63588761 BTC contro 20.053.779,26088761 del livello 1 → differenza **59,375 BTC = 19 blocchi × 3,125 ESATTI**. Le fee non creano moneta (la spostano dai mittenti ai miner): il totale cresce solo del sussidio. Due misure indipendenti, a 19 blocchi di distanza, si validano a vicenda al satoshi. ```text === Census by script type === (altezza 957.301) type pubkey entries BTC P2PK uncompressed key EXPOSED 34,343 1,709,682.97233657 P2PK compressed key EXPOSED 10,271 6,136.56019352 bare multisig EXPOSED 2,621,788 70.29199448 P2TR (Taproot) EXPOSED 54,433,089 216,699.01357424 P2PKH (1…) behind hash 44,412,522 4,599,483.71774068 P2SH (3…) behind hash 12,144,817 3,937,453.24907191 P2WPKH (bc1q…, short) behind hash 49,599,918 8,163,121.81137845 P2WSH (bc1q…, long) behind hash 2,853,141 1,418,572.89611611 witness other/future other 93,402 0.51578406 other / non-standard other 20,770 2,617.60769759 TOTAL 166,224,061 20,053,838.63588761 key exposed by construction (certain lower bound): 57,099,491 entries, 1,932,588.83809881 BTC (9.64% of total) ``` Che cosa dicono questi numeri: - **limite inferiore certo: ~1,93M BTC (9,64%)** a chiave esposta per costruzione, SENZA contare il riuso (che da solo porta le stime pubbliche a 25-34%: il nostro numero è il pavimento verificabile, non il totale); - **la domanda «era Satoshi», coi nostri numeri**: dei ~1,716M BTC in P2PK, **1.658.384 BTC (~97%) sono nati sotto l'altezza 100.000** (≈ prima di fine 2010); 1,294M sotto la 50.000. La distribuzione per epoca conferma: P2PK è un fossile dell'epoca 1, quasi nulla dopo il 2012; - P2TR è l'opposto: 54,4M di voci ma solo ~217k BTC, quasi tutti nati in epoca 5 — tante monete, poco valore, tutto recente; - curiosità onesta da raccontare: il multisig bare ha 2,6M voci e SETTANTA BTC — polvere, in gran parte residuo dell'uso di quegli script per incidere dati in catena (2013-2014). Le voci non sono valore; - «other / non-standard»: 20.770 voci, ~2.617 BTC (include P2PK con chiavi malformate e script esotici): categoria dichiarata, non nascosta. **Decisione di lingua:** codice, commenti, identificatori e output testuale degli strumenti sono in INGLESE — nascono per il repo pubblico internazionale, e l'output inglese non è una barriera (come non lo è il JSON di `bitcoin-cli`). Lo strato didattico in italiano è QUESTO manuale, che mostra e spiega l'output. Di conseguenza il controllo degli indirizzi si invoca `nodsig check`, e non con un nome italiano. **Perché Python e non C:** (1) il codice è parte dell'argomentazione — «leggi tu stesso che cosa conta» funziona se le ~300 righe si leggono quasi come pseudocodice, non se la logica annega tra buffer e puntatori; (2) niente compilazione = niente nuova fiducia: un binario è ciò che vogliamo evitare, un sorgente C andrebbe compilato (toolchain, piattaforme), `python3` gira identico ovunque con la sola libreria standard; (3) sicurezza della memoria: 12 GB di parsing binario in C = classe di vulnerabilità, in Python al peggio un'eccezione chiara; (4) la velocità di C non compra nulla: 15 minuti una volta per pubblicazione, il collo di bottiglia è il dump sul Pi (ore); (5) è il differenziale dichiarato nella ricognizione (i parser seri sono tutti compilati; il riferimento di Core è in Python come noi). ### Livello 3 — il riuso (giorni, opzionale) La domanda del terzo livello: quante monete stanno oggi dietro serrature già aperte in passato. «Opzionale» in un senso preciso: l'esposizione totale è fatta di due addendi, e i primi due livelli chiudono da soli il primo — il pavimento per costruzione, il 9,64% — che è già un risultato completo e verificabile. Questo livello aggiunge il secondo, il riuso; chi si ferma prima non ha un numero monco, ha un pavimento che per costruzione sbaglia solo per difetto. Il prezzo del secondo addendo: una passata intera sulla storia della catena, giorni di macchina non assistiti. Ed è l'unico livello che si possa anche lasciare a metà avendo comunque un numero onesto: ogni blocco letto può solo aggiungere bruciati, mai toglierne. Il disegno che lo regge — perché una passata sola basti, e che cosa esattamente si confronta — sta in «Le tre idee del livello 3», nella parte sul disegno; qui ci sono i comandi. **La forma del risultato:** esposizione totale = pavimento per costruzione (9,64% a 957.301) + riuso a perimetro dichiarato, da confrontare col >34% di BIP-361. I due addendi, coi numeri veri della nostra run, escono dai passi qui sotto; che cosa dicono è il mestiere del post dati. > Eseguito e pubblicato: i comandi, i tempi e le impronte qui sotto sono > quelli reali della nostra esecuzione al blocco 957.301, con la versione > 1.0.0 degli strumenti. Chi ripete la sequenza alla stessa altezza ottiene, > per costruzione, le stesse impronte. Una precisazione sui numeri di versione, prima che le due numerazioni si incontrino: gli **strumenti** sono alla release 1.0.0; i **formati** che scrivono — `reveal-archive-v2`, `graph-v2`, e così via — portano il numero della propria specifica, che è una scala separata e documentata nel repo (`docs/formats/`). Un formato cambia numero solo quando cambiano i byte che lo compongono, non quando esce una release: il `-v2` nei nomi qui sotto non allude a un «nodsig 2». Una regola prima di tutti i passi: si sceglie UN'altezza e la si usa ovunque. Un artefatto è definito da dove si ferma, e pezzi tagliati ad altezze diverse non si uniscono. Al lettore basta **una sola scansione della catena**, la strada dell'archivio (`nodsig archive`): da lì si sigilla l'archivio e si derivano la tabella del riuso e la curva. Le strade che abbiamo percorso noi sono due, perché la seconda serviva a controllare la prima per una via indipendente. Quel controllo è un **risultato**, riportato in coda ai passi: non è un compito che il lettore debba rifare. I tempi indicati sono i nostri, su un nodo modesto (Umbrel su Raspberry Pi, blocchi via RPC su tunnel SSH): dipendono dall'hardware e dal canale, prendili come ordine di grandezza. L'ETA che gli strumenti stampano a ogni checkpoint è ingenua (proietta il ritmo corrente, ma i blocchi si riempiono salendo di altezza): fidati dei timestamp, non di quella. Una tabella di tempi è più utile quando descrive l'hardware che qualcuno ha già, non quello che lo strumento preferirebbe: la nostra è misurata sul setup lento. **Passo 1 — `prepare`: distilla le serrature correnti dallo snapshot.** - **Obiettivo:** dalla `utxos.dat` del livello 2, la lista delle serrature «dietro hash» oggi nell'UTXO set, in file ordinati e ricercabili (`locks_p2pkh.bin`, `_p2sh`, `_p2wpkh`, `_p2wsh`). - **Serve a:** è l'insieme-bersaglio contro cui si controlla ogni rivelazione, e l'input di `derive`/`crosscheck`. - **Richiede:** lo snapshot del livello 2 (`utxos.dat`, stessa altezza 957.301). - **Si compone di:** `nodsig reuse prepare`. ```bash nodsig reuse prepare "$SNAPSHOT" --out "$LOCKS" ``` - **Verifica:** i conteggi per tipo devono corrispondere all'UTXO set del livello 2 — nella nostra run 21.267.062 p2pkh, 6.240.320 p2sh, 23.060.044 p2wpkh, 1.166.793 p2wsh. Rapido (una passata sullo snapshot). **Passo 2 — `scan`: la passata unica sull'archivio delle rivelazioni.** - **Obiettivo:** percorrere la catena fino all'altezza dello snapshot registrando ogni rivelazione di chiave/script — insieme all'altezza della sua prima apparizione — in un archivio appendibile (`reveal-archive-v2`). Con `--graph`, la stessa passata co-emette il grafo grezzo (`graph-v2`); con `--headers`, l'archivio degli header (~150 MB), che rende ripetibili offline i controlli di integrità della passata e dà le date di calendario ai comandi che le vogliono, senza più chiedere il nodo. - **Serve a:** è la fonte da cui `derive` ricava riuso e curva e `merge` sigilla l'archivio; il grafo alimenta le capacità storia e co-spese del controllo, e le ri-derivazioni per epoca. - **Richiede:** il nodo raggiungibile (vedi «Raggiungere bitcoin-cli»; per il tunnel SSH e il cookie vedi i Prerequisiti; `--rest` è l'alternativa più magra per il fetch dei blocchi). NON richiede il `prepare`: l'archivio registra tutto, il confronto con le serrature avviene dopo. - **Si compone di:** `nodsig archive scan`. ```bash nodsig archive scan \ --rpc http://127.0.0.1:8332 --cookie-file "$COOKIE" \ --end 957301 --archive "$ARCHIVE" --graph "$GRAPH" --headers "$HEADERS" \ 2>&1 | tee -a ~/utxo_work/scan.log ``` `--graph` è facoltativo: ometterlo se non serve costruire poi l'indice e i derivati. Facoltativo anche `--headers`, che però costa 150 MB e non ore: la convenienza è quasi sempre dalla sua. Esiste un terzo co-emesso, `--nonces` (59,7 GB e ~10% di CPU in più): il censimento dei punti nonce di ogni firma, l'unica cosa che una passata successiva non potrebbe ricostruire, perché gli artefatti conservati non contengono dati di sblocco. Risponde a un'altra domanda — se una chiave, firmando messaggi diversi col medesimo nonce, si sia resa ricavabile — e ha la sua pagina nel repo (`docs/nonce-check.md`): questo manuale non la copre. - **Verifica:** a fine passata l'archivio copre le altezze 1..957.301. Nella nostra run: 7.704.658.388 rivelazioni in 2.787 run, `malformed scriptSigs: 0`. Durata: 58 h 47 sul nostro setup (dominata dal filo verso il Pi). **Passo 3 — `derive`: la tabella del riuso e la curva, prima del sigillo.** - **Obiettivo:** dall'archivio più le serrature, la tabella dei bruciati per tipo e la curva del riuso vs quota di storia letta. - **Serve a:** sono i numeri pubblicati dell'esposizione da riuso e i dati della curva. - **Richiede:** l'archivio del passo 2 e le serrature (`$LOCKS`, passo 1). **Va eseguito PRIMA del `merge`**: la curva nasce dalla piastrellatura a intervalli delle run di scansione, e la fusione spende quella piastrellatura per produrre la forma canonica. Prima si deriva, poi si sigilla. - **Si compone di:** `nodsig archive derive`. ```bash nodsig archive derive \ --archive "$ARCHIVE" --locks "$LOCKS" \ --curve ~/utxo_work/archive_curve_957301.csv \ 2>&1 | tee -a ~/utxo_work/derive.log ``` - **Verifica:** TOTALE **8.784.364** serrature bruciate su **5.084.725,41330985 BTC** (p2pkh 3.925.128 / 1.185.751,57; p2sh 1.155.896 / 1.283.224,01; p2wpkh 3.612.318 / 1.924.332,48; p2wsh 91.022 / 691.417,35), perimetro `faces=on, cosigners=on`, impronta **`5fd579dbc6190f5bf08e35947ab77a55e067aa366acd13bd49fa30d3ba9d0ee7`** (il GATE «serrature/curva»), curva a 96 righe sulla griglia da 10.000. Nostro tempo: 5 h 39. La curva è un CSV, e si rilegge con un comando suo: `nodsig curve deltas archive_curve_957301.csv` la mostra intervallo per intervallo, cioè con che ritmo il riuso è cresciuto invece che a quanto è arrivato. `nodsig curve dates` le appaia le date vere dei blocchi: con l'archivio degli header in mano le ricava da lì, offline; senza, le chiede al nodo — e in entrambi i casi quel join si dichiara invece di contrabbandarlo, perché i timestamp nella curva non ci sono. **Passo 4 — `merge`: fondi le run e sigilla l'archivio.** - **Obiettivo:** fondere le run in un archivio ordinato e calcolarne l'impronta canonica. - **Serve a:** l'archivio sigillato è ciò che il controllo legge per la capacità esposizione, e ciò che `crosscheck` e `lookup` interrogano. - **Richiede:** l'archivio del passo 2, con la curva già derivata (passo 3). - **Si compone di:** `nodsig archive merge`. ```bash nodsig archive merge --archive "$ARCHIVE" 2>&1 | tee -a ~/utxo_work/merge.log ``` - **Verifica:** stampa `merged through height 957,301` e l'impronta **`aacaf02dca2fc5ba8532e54fa75159041fc99051efa68eb63e59bc9537369ced`** (è il GATE «archivio» dei post). Nostro tempo: 4 h 11. **Passo 5 (se hai usato `--graph`) — sigillo del grafo.** ```bash nodsig graph fingerprint --graph "$GRAPH" ``` - **Verifica:** impronta **`a014f787256e1831c90290e04c2adbcf1fe00cfc3f2d26bb668bff491aa54190`** (301.123.338.474 byte, 1.393.498.473 tx). Nostro tempo: 5 h 13 (sola rilettura, via LAN: è la misura di luglio sugli stessi byte — il sigillo nuovo cambia la ricetta dell'impronta, non la lettura). È il GATE «grafo» e la base della catena di provenienza per indice e derivati. **Il controllo incrociato: fatto una volta, riportato qui.** I cinque passi qui sopra sono la strada che basta. Ne esiste una seconda — ripercorre la catena confrontando ogni rivelazione con le serrature mentre passa, senza costruire nessun archivio — e serve a rispondere a una domanda che è legittimo porsi davanti a qualunque conteggio: e se il totale fosse un artefatto di questo codice invece che un fatto della catena? L'abbiamo percorsa una volta, alla stessa altezza, e il confronto ha chiesto alle due strade lo stesso numero: ``` TOTALE 8.784.364 serrature, 5.084.725,41330985 BTC impronta 5fd579db…ba9d0ee7 (la stessa del passo 3) CHECK PASSED: the two independent roads meet on the same fingerprint. ``` Nostro tempo del solo confronto: `real 258m` (~4h20m); la scansione diretta che lo precede costa un'altra passata intera sulla catena. È il motivo per cui questo è un risultato da ereditare e non un compito da assegnare: chi lo vuole rifare trova i comandi nel repo, e come le due strade sono fatte — e perché non si somigliano — sta in «Le due strade e la run senza assistenza», nella parte sul disegno. Il limite va detto insieme all'esito: le due strade condividono il lettore dei blocchi e le serrature di partenza, quindi il controllo prova il **modo di contare**, non il modo di leggere un blocco. La lettura ha una garanzia sua, che copre anche la strada unica di questo capitolo: gli hash si ricalcolano mentre si legge — blocco chiesto, radice di Merkle, aggancio al precedente — e un byte fuori posto ferma la passata prima che diventi un numero. Una cosa riguarda invece anche chi fa solo la strada breve: **il perimetro (`faces`/`cosigners`) va tenuto identico tra `scan` e `derive`**. I flag `--no-faces`/`--no-cosigners` esistono per esplorare perimetri più stretti, ma vanno rispecchiati ovunque, o il confronto si rifiuta. ### Indice degli outpoint e derivati (`nodsig index`, `nodsig derived`) Questo capitolo è facoltativo e sta a valle del livello 3. Serve a chi vuole interrogare la catena su tre domande che l'archivio delle rivelazioni non copre: la storia di una serratura, la fee di una transazione, che cosa è stato speso insieme a che cosa. Sono le tre capacità che il controllo degli indirizzi aggancia con `--index` e `--derived`, ed è la ragione per cui al passo 2 del livello 3 conviene passare `--graph`. L'impianto, che vale la pena capire prima dei comandi. L'indice **numera la catena una volta sola**: ogni transazione e ogni output ricevono il loro numero d'ordine, e un file i cui record stanno in quell'ordine non ha bisogno di ripetere nessuna chiave, perché il record n-esimo sta all'n-esima posizione e si legge con un salto, senza cercare. La domanda «l'outpoint (txid, indice) quale output è?» la risolve un solo file ordinato per txid; gli input si agganciano ai loro output con una fusione fra due flussi ordinati, non con accessi casuali su centinaia di gigabyte. Il guadagno si paga una volta e si riscuote sempre: ogni derivato che vorrà citare un output ne cita il numero, cinque byte invece dei trentasei della coppia (txid, indice). I derivati riordinano poi gli stessi fatti per serratura, per transazione e per co-spesa: un derivato costoso, tre domande, un join lineare al posto di tre indici separati. Il confine dichiarato altrove vale anche qui: «indirizzo» significa serratura identica (stessa impronta di scriptPubKey), non il portafoglio, e non la stessa chiave sotto forme diverse. **Spazio e tempo, prima di cominciare.** L'indice occupa 248,5 GB e i derivati 190,5 GB, che si sommano ai 301,1 GB del grafo di partenza: per la catena completa mettine in conto circa 740, a cui l'archivio delle rivelazioni ne aggiunge altri ~98 se lo si tiene (il bilancio di tutto, e la lista di che cosa si può cancellare dopo, sono nella sezione degli artefatti in coda). (Le tabelle qui sotto contano in GiB, cioè la stessa cosa in binario: 248,5 GB sono 231,42 GiB. È il motivo per cui più avanti lo stesso indice sembra rimpicciolire.) La nostra build ha impiegato 23 h 24 per l'indice e 13 h 44 per i derivati. Il carico è I/O, non CPU, e quel che decide i tempi è il disco sotto gli artefatti: misurato sullo stesso hardware e sullo stesso file, un mount di rete 9p legge a 14,4 MB/s, uno CIFS a 45 (75 in scrittura), un disco USB locale a 75 (97 in scrittura). La sola scelta del mount sposta i tempi di 3-5 volte; le durate qui sopra vengono dai due montaggi più veloci. Sono ore di macchina, non di sorveglianza: entrambi i comandi scrivono checkpoint, e dopo un'interruzione si rilancia lo STESSO comando, che riprende invece di ricominciare. **Tenerli aggiornati costa altro, e conviene saperlo prima di dimensionare il disco.** Gli artefatti si estendono con lo stesso comando della prima volta, ma due conti cambiano. Una fusione scrive la generazione nuova prima di cancellare la vecchia, quindi vuole libero all'incirca il doppio dei file che sta fondendo: è il prezzo della garanzia che un'interruzione lasci intera quella di prima. E un append rilegge `spends.bin` per intero, una volta per esecuzione e non per blocco, perché quel file viene riordinato a ogni fusione e una posizione al suo interno non sopravvive. Ne segue una regola pratica: conviene lasciar accumulare i blocchi e appenderli insieme, perché uno alla volta paga quella passata ogni volta. **Passo 1 — `index build`: numerare gli outpoint.** - **Obiettivo:** dal grafo `graph-v2`, un indice sigillato `outpoint-index-v2`: la coordinata ordinale di ogni output della catena, e accanto a ciascuno la sua spesa già risolta. - **Serve a:** è l'unico input dei derivati, e da solo risponde già a `lookup`, cioè alla storia completa di un outpoint (quando è nato, quanto vale, sotto quale serratura, da chi e quando è stato speso). - **Richiede:** il grafo del livello 3 (passo 2 con `--graph`, sigillato al passo 5). Il nodo non serve più: da qui in poi si lavora sui file. - **Si compone di:** `nodsig index build`, poi `verify` e `stats`. ```bash nodsig index build --graph "$GRAPH" --index "$INDEX" --end 957301 \ 2>&1 | tee -a ~/utxo_work/index.log nodsig index verify --index "$INDEX" --graph "$GRAPH" # ogni byte contro il manifest, ogni scala ricostruita, il padre confermato nodsig index stats --index "$INDEX" # fase, watermark, conteggi, impronta ``` `--end 957301` congela l'altezza allo snapshot pubblicato: stessa altezza, stessa impronta, su qualunque macchina. È il contratto di replica, ed è anche ciò che rende i tempi confrontabili tra ambienti diversi. - **Verifica:** `verify` rilegge tutto — e ricostruisce ogni scala di ricerca dal file che indicizza, così una scala è controllata per essere GIUSTA e non solo integra — poi stampa `fingerprint verified:` con l'impronta; `stats` dice le stesse cose leggendo il manifest, ed è istantaneo. Nella nostra run: ``` phase: sealed heights 1..957,301 transactions 1,393,498,473 outputs 3,819,356,162 inputs seen 3,417,883,234 spends_g0002.bin 3,417,883,234 records txid_index_g0001.bin 1,393,498,471 records overwritten txids: 2, duplicate spends: 0, unresolved: 0 fingerprint: 338c6c48f6e6c806c6d0a494bb9ca5060adcb83167c0db45328d39b40b14a69d ``` L'ultima riga di conteggio è l'igiene dell'indice e va letta, non scorsa. `overwritten txids: 2` sono le due coinbase gemelle di BIP-30, un fatto noto della catena (ed è il motivo per cui il `txid_index` ha due record in meno delle transazioni: a parità di txid vince l'ultima, che è appunto la semantica di BIP-30). `duplicate spends: 0` e `unresolved: 0` dicono che ogni input ha trovato il suo output e che nessun output risulta speso due volte. Un indice che tollera buchi si dice largo, e i derivati si rifiutano di nascerci sopra. Dimensioni reali per file, 231,42 GiB in tutto: | file | GiB | |---|---:| | `outputs.bin` | 99,60 | | `txid_index_g0001.bin` | 51,91 | | `txids.bin` | 41,53 | | `spends_g0002.bin` | 31,83 | | `tx_first_out.bin` | 6,49 | | `blocks.bin` | 0,01 | | scale `.lad` (3 file) | 0,05 | **Passo 2 — `derived build`: storia, fee e co-spese.** - **Obiettivo:** tre file ordinati sopra l'indice. `history_g0001.bin` tiene una riga per output, e quella riga porta entrambi gli eventi, l'incasso e la spesa; `tx_inputs.bin` raccoglie gli input per transazione; `fees.bin` una fee per transazione. - **Serve a:** le capacità storia, fee e co-spese, sia da riga di comando sia agganciate al controllo degli indirizzi con `--derived`. - **Richiede:** un indice sigillato e stretto (passo 1). `derived build` rifiuta un indice con `unresolved > 0`, e controlla per impronta di essere agganciato a QUELL'indice: un accoppiamento stantìo non passa. - **Si compone di:** `nodsig derived build`, poi `verify` e `stats`. ```bash nodsig derived build --index "$INDEX" --out "$DERIVED" \ 2>&1 | tee -a ~/utxo_work/derived.log nodsig derived verify --derived "$DERIVED" nodsig derived stats --derived "$DERIVED" ``` Il comando attraversa quattro fasi (`scan`, `merge-history`, `merge-inputs`, `seal`) guidate dal proprio file di stato, senza rilanci a mano. Le due fusioni non stampano nulla fino alla riga finale: il silenzio è normale, non un blocco. - **Verifica:** ``` phase: sealed cursors: 3,819,356,162 outputs, 3,417,883,234 spends, 1,393,498,473 txs fees.bin 1,393,498,473 records history_g0001.bin 3,819,356,162 records tx_inputs.bin 3,417,883,234 records fees total 30,047,764,560,047 sats, updated rows 0 fingerprint: 44689372f169a5c503bdf128a082c31fef767e35c77696e8e60843b42afa1c80 ``` Le righe di `history` sono esattamente quante gli output dell'indice, e si spartiscono in 3.417.883.234 spesi più 401.472.928 non spesi; le serrature distinte sono 1.554.718.932; le fee totali 300.477,64560047 BTC. Dimensioni reali per file, 177,46 GiB in tutto (le voci sono arrotondate al centesimo e il totale no, quindi sommandole viene un centesimo in meno): | file | GiB | |---|---:| | `history_g0001.bin` | 135,17 | | `tx_inputs.bin` | 31,83 | | `fees.bin` | 10,38 | | scale `.lad` (2 file) | 0,07 | Il `seal` non si limita a sommare: prima di scrivere l'impronta pretende un'identità incrociata, cioè che i satoshi che `history` dà per spesi coincidano con quelli consumati dal conto delle fee. Sono due cammini indipendenti che devono incontrarsi su un numero solo; se non tornasse, il sigillo si fermerebbe invece di scriversi. **La catena di provenienza.** L'impronta di un artefatto copre ciò che l'artefatto È — tag di formato, copertura, digest dei file — e nient'altro: due build oneste della stessa catena alla stessa altezza concordano sull'impronta, chiunque le abbia fatte e da qualunque copia degli strumenti. Da dove viene è una domanda separata, con una risposta separata: ogni manifest dichiara il padre accanto all'impronta — i derivati nominano l'indice, l'indice il grafo — e `verify` conferma l'aggancio quando gli si passano entrambi. Chi rifà il percorso alla stessa altezza deve ritrovare la stessa catena: ``` grafo a014f787256e1831c90290e04c2adbcf1fe00cfc3f2d26bb668bff491aa54190 └─ indice 338c6c48f6e6c806c6d0a494bb9ca5060adcb83167c0db45328d39b40b14a69d └─ derivati 44689372f169a5c503bdf128a082c31fef767e35c77696e8e60843b42afa1c80 ``` E controllare costa poco, in proporzione. Nella nostra run: `archive verify --deep` 1 h 41, `index verify --graph` 1 h 03, `headers crosscheck --index` 59 minuti, `derived verify --index` 42. Il controllo di tutto costa circa un ventesimo della costruzione, ed è la ragione per cui non è il passo da saltare. **Tornare a un'altezza già coperta (`rewind`).** Il contratto di replica ha due strade, non una. Costruire con `--end 957301` arriva a quello stato da sotto; `rewind` ci arriva da sopra, riportando un artefatto sigillato a un'altezza che già copriva, e negli stessi byte che avrebbe scritto una build fermatasi lì. Serve esattamente a chi tiene gli artefatti al passo con la catena: per controllare i numeri di questo manuale bisogna stare a 957.301, e senza `rewind` l'unica strada sarebbe ricostruire tutto da capo. ```bash nodsig index rewind --index "$INDEX" --graph "$GRAPH" --to-height 957301 nodsig derived rewind --index "$INDEX" --derived "$DERIVED" ``` Prima l'indice, poi i derivati, che non scelgono la propria copertura ma seguono l'indice: è lo stesso ordine in cui una build li estende. Il grafo serve per una cosa sola, l'hash del blocco a quell'altezza, che l'indice conserva ma non sa ricalcolare. Costa una passata di filtro per file più il sigillo, non una catena da rileggere, e la ragione sta in una proprietà sola: togliere record da un file ordinato lo lascia ordinato, quindi non c'è nulla da riordinare. I file che crescono in coda si troncano ai conteggi che `blocks.bin` conosce già per ogni altezza; gli altri si rifondono lasciando cadere ciò che sta sopra il taglio. Che funzioni non va creduto sulla parola nemmeno qui: riavvolgere a 957.301 deve restituire le due impronte stampate poco sopra, quella dell'indice e quella dei derivati. Se non tornano, il riavvolgimento è sbagliato, e il confronto lo dice senza bisogno di fidarsi di noi. Un caso non si può servire, e il comando si ferma invece di provarci. Se il taglio cade fra le due istanze di un txid duplicato, cioè le coinbase gemelle di BIP-30 alle altezze 91.722/91.812 e 91.842/91.880, il record più vecchio è stato sovrascritto al momento della costruzione e non si recupera: un riavvolgimento farebbe sparire quel txid, mentre una ricostruzione ce l'avrebbe. Fuori da quelle due finestre il problema non esiste, e dopo BIP-34, che mette l'altezza nella coinbase, non può più nascerne uno nuovo. **I file `.lad` non entrano nell'impronta.** Sono scale: un campione ogni poche migliaia di chiavi, che risparmiano alla ricerca la bisezione cieca sul file grande. Sono cache, non dati: se mancano, la ricerca ripiega su una bisezione diretta, più lenta e con la stessa risposta. Per questo stanno fuori dal calcolo dell'impronta, e per questo un artefatto costruito prima che le scale esistessero resta valido. ## Chiedere ### Il tuo indirizzo è esposto? Per sapere se un proprio indirizzo è esposto non serve conoscere la propria chiave pubblica, né alcuno strumento particolare: bastano l'indirizzo e una domanda sola. La chiave che lo protegge è già comparsa in chiaro sulla catena? La risposta dipende dal tipo di indirizzo. Un indirizzo Taproot (`bc1p…`) è esposto per costruzione: l'indirizzo è già la chiave pubblica, ne è soltanto la forma leggibile. Non c'è nulla da verificare, ma riguarda il tipo più recente, non i più diffusi. Gli indirizzi «a impronta» (`1…`, `3…`, `bc1q…`) tengono invece la chiave nascosta dietro un hash finché non la si usa: la chiave si rivela solo alla prima spesa, quando la firma va allegata in chiaro perché i nodi possano validarla. Per questi la verifica è una domanda secca, «questo indirizzo ha mai speso?», con tre esiti: - solo ricezioni, nessuna spesa: protetto, la chiave non è ancora in catena; - ha speso e conserva ancora un saldo: riuso — esposto coi fondi ancora in palio, ed è il caso che il resto della sezione insegna a chiudere; - ha speso e il saldo è a zero: esposto, ma senza niente in palio (basta non riusarlo). **Come guardarlo senza affidarsi a terzi.** Un explorer eseguito in casa (l'app mempool su Umbrel, per esempio) risponde subito: si cerca l'indirizzo e si guarda se ha almeno una transazione in uscita. Il controllo degli indirizzi automatizza lo stesso criterio su una lista intera. La regola di privacy è una sola e non si deroga: i propri indirizzi si danno al proprio nodo, mai a un explorer pubblico. **L'esposizione è della chiave, non dell'indirizzo.** È la distinzione che conta, e porta con sé tre conseguenze. Un indirizzo che ha speso resta bruciato per sempre: anche i fondi che vi arrivassero in futuro nascerebbero esposti, perché la catena non dimentica. La stessa chiave può presentarsi con più facce (un `1…`, un `bc1q…`, un `3…` che la incapsula): esposta la chiave, sono esposte tutte le sue facce. Gli altri indirizzi di un portafoglio moderno restano invece al sicuro, perché ognuno nasce da una chiave diversa, e da una chiave figlia esposta non si risale né al seme né alle sorelle: la derivazione passa per un hash, che il calcolo quantistico non scioglie. L'unica eccezione è l'xpub condiviso, che espone un intero ramo, ma fuori dalla catena. Da qui il comportamento sano, che molti portafogli adottano già da soli: incassare una volta, spendere svuotando, mandare il resto su un indirizzo nuovo. Un indirizzo esposto ma vuoto è innocuo; il caso da evitare è il riuso, cioè tornare a incassare su un indirizzo che ha già speso. **«Se sposto tutto su un indirizzo nuovo, sono protetto?»** Sì, a tre condizioni. La destinazione deve essere di tipo «a impronta» (`bc1q…`, `1…`) e mai usata prima, non un Taproot, che nasce con la chiave in vista ed è quindi il tipo sbagliato per questo scopo. La protezione dura finché il comportamento resta sano, senza riusi successivi e senza xpub in giro. E durante la transazione stessa le chiavi degli input restano visibili per qualche minuto nella mempool: oggi è ininfluente, ed è il motivo per cui la mossa va letta come prevenzione, da fare prima che esistano macchine capaci di sfruttare quella finestra. **Perché ogni spesa espone, e la ricezione no.** La firma di una transazione si verifica con la chiave pubblica, quindi la transazione la porta con sé in chiaro: diversamente i nodi non potrebbero validarla. Ricevere non rivela nulla, salvo il caso Taproot. E la rivelazione non va verso un solo nodo: la transazione si propaga a tutta la rete in pochi secondi, la vede chiunque sia in ascolto, non solo chi mina. La conferma nel blocco non richiude la chiave, che resta lì per sempre: il pericolo cessa non perché la chiave torni segreta, ma perché la moneta si è mossa. **Una spesa parziale non salva.** Una transazione muove le singole monete che sceglie, non «l'indirizzo»: i fondi lasciati su un indirizzo che ha già speso restano esposti in permanenza, anche senza nuovi incassi. La regola compatta non è «non riusare», è «quando spendi, svuota»: incassare una volta, spendere per intero, mandare il resto su un indirizzo nuovo. Molti portafogli lo fanno in automatico, ma non è garantito ovunque, e la risposta «ha speso e conserva un saldo: esposto» copre proprio questo caso. **Un esito che sorprende.** Un indirizzo può risultare esposto anche senza aver mai speso di suo, se la sua chiave è comparsa dentro uno script rivelato da altri: per esempio un cofirmatario di uno script a più firme che ha speso. La risposta legge la catena, non le aspettative, e per questo conviene controllare invece di dedurre. **Il caso dell'xpub.** Un seme non ha una sola xpub, ma una per ogni ramo di derivazione (quella che di solito si esporta è il primo account del ramo in uso). Un'xpub trapelata compromette quel sottoramo, non il seme: verso l'alto la protegge la derivazione via hash, verso i lati la separazione tra account. In teoria basta passare a un account nuovo dello stesso seme; in pratica, se non si sa con certezza cosa ha visto il servizio, un seme nuovo è la risposta senza asterischi. La scala del problema, per ora, è di privacy più che di fondi: lo diventa solo il giorno in cui esisteranno macchine capaci, e vale la logica del «raccogli oggi, decifra domani» (ciò che il servizio ha visto, l'ha visto per sempre). Il trasloco non è urgente, ma conviene farlo prima, nello stesso calendario largo della migrazione. **Cosa resta invisibile.** L'esposizione fuori catena, come l'xpub qui sopra, non lascia traccia da nessuna parte. E per gli indirizzi a più firme lo script interno resta nascosto fino alla spesa: «protetto», in quel caso, parla dell'hash, non di chi potrebbe spendere dietro di esso. ### Il controllo degli indirizzi (`nodsig check`) Dato un elenco di indirizzi, dice per ciascuno che cosa risulta dalle fonti che possiedi: se la chiave che lo protegge è già comparsa sulla catena, quanto ci sta dietro adesso, che cosa è entrato e uscito, e con quali altre monete è stato speso. Ogni risposta viene da una capacità distinta, e l'insieme delle capacità è fatto per crescere: quando non c'è la fonte per rispondere, la risposta è «non determinabile» con la ragione scritta, mai un silenzio e mai una supposizione. Automatizza il criterio della sezione qui sopra su una lista intera, il proprio portafoglio storico per esempio, interrogando solo infrastruttura propria. **Perché serve un indice, e perché Core non ce l'ha.** Bitcoin Core non sa rispondere a «questo indirizzo ha mai speso?», e non è una mancanza: il lavoro del nodo è validare, e la validazione non parla mai di indirizzi. Chiede solo se una moneta esiste, se è ancora non spesa e se i dati di sblocco soddisfano la sua serratura, domande a cui basta l'UTXO set, indicizzato per moneta e non per proprietario. A rigore gli indirizzi sulla catena nemmeno esistono: sono una codifica leggibile di alcuni pattern di script. La storia completa c'è, ma archiviata in ordine di arrivo, come una biblioteca catalogata per scaffale: per rispondere «per indirizzo» va letta tutta una volta e va costruito un catalogo a parte. È ciò che fanno Electrs o mempool self-hosted, ed è ciò che facciamo qui, perché **un indice non è altro che una scansione fatta una volta e archiviata**: un costo pagato una volta e ammortizzato su ogni domanda successiva. Chiunque può costruirsi il proprio, tenendo ciò che serve al suo scopo. Gli indicizzatori stanno fuori dal protocollo, e si vede da qui: sparissero tutti, nessun blocco cambierebbe validità. **Un'interfaccia per capacità.** Una risposta si compone interrogando capacità distinte, ognuna con il suo backend, ognuna sostituibile senza toccare le altre: | Capacità | La domanda | Da dove risponde oggi | |---|---|---| | esposizione | la chiave o lo script dietro questo indirizzo sono mai stati rivelati sulla catena? | il nostro archivio delle rivelazioni | | saldo | quanti satoshi ci stanno adesso? | `scantxoutset` sul nodo, che accetta più descrittori in una chiamata sola: **una scansione per l'intera lista**, non una per indirizzo (su un Raspberry Pi è la differenza fra minuti e ore) | | storia | quali monete sono entrate, quali uscite, che cosa resta? | indice degli outpoint e derivati | | co-spese | che cosa è stato speso insieme alle sue monete? | gli stessi due artefatti | Il default che pubblichiamo non è l'unica strada: **Electrs, o Fulcrum, resta un backend di pari dignità** per chi preferisce far girare sul proprio nodo un indicizzatore già pronto. È esattamente a questo che serve l'interfaccia, e la sua implementazione è nel piano di sviluppo, con le istruzioni per allestirlo. Tre proprietà la rendono un pregio e non impalcatura. Un backend mancante non finge: la capacità si degrada a «non determinabile» con la ragione scritta, mai a un falso «protetto», che è la stessa regola con cui l'archivio tratta l'assenza. Il silenzio e il «no» sono risposte diverse, e confonderli è il modo in cui gli strumenti ingannano le persone sui loro stessi soldi. Aggiungerne uno è una classe sola registrata in un punto solo, e nient'altro cambia. I default sono una scelta dichiarata, e i flag esistono perché un terzo ne esplori altri, con la stessa filosofia dei flag di perimetro dello scanner. Il codice, insomma, non sa da dove arriva una risposta: sa solo quale domanda sta facendo. Il nodo, in particolare, viene contattato SOLO se si passa `--rpc`; senza, niente saldo e nessuna chiamata, proprietà che serve anche al collaudo, che gira senza nodo. **Default su file.** La regola di privacy è nel codice, non nelle raccomandazioni: il rapporto va in `check-results.txt` (o `--out PATH`) con l'avviso di sensibilità in testa, perché uno schermo si condivide e un terminale si registra; a video solo con `--stdout` esplicito, pensato per le fixture pubbliche e per il piping. Su stderr resta il puntatore al file, mai una risposta. La decodifica degli indirizzi è aritmetica pura, senza rete (base58check per `1…` e `3…`, bech32 e bech32m per `bc1…`, dalle spec BIP-173 e BIP-350). Il collaudo la aggancia a vettori pubblici noti e la incrocia con un encoder speculare scritto nel test, che serve anche a fabbricare indirizzi i cui digest combaciano con l'archivio sintetico: l'intera prova di esposizione gira su dati pubblici, senza un solo indirizzo reale. #### Usare il controllo — i comandi **Obiettivo** — dato un elenco di indirizzi, un rapporto per CIASCUNO: tipo, risposta di esposizione, e (se agganci i backend) saldo, storia e co-spese. Esce su `check-results.txt` (più `--csv` se lo chiedi). **Serve a** — rispondere «i MIEI indirizzi sono esposti?» interrogando solo il tuo nodo e i tuoi file, senza dare la lista a nessun terzo. **Richiede** — gli artefatti delle capacità che vuoi agganciare: l'archivio delle rivelazioni per l'esposizione, indice e derivati per storia e co-spese. L'archivio nasce dalla pipeline del livello 3; indice e derivati dal capitolo che la segue, «Indice degli outpoint e derivati». **Il repo non pubblica questi artefatti**, che sono grossi: chi li vuole li ricostruisce, e le impronte dicono se la ricostruzione è giusta. Un controllo senza archivio non mente, degrada onesto a «non determinabile». Il nodo serve solo per il saldo, ed è contattato solo con `--rpc` — e chi glielo chiede gli fa conoscere la lista degli indirizzi: è il motivo per cui quel flag si usa col PROPRIO nodo e con nessun altro. **Comandi.** La risposta minima non ha bisogno di nulla — decodifica il tipo e riconosce subito i Taproot, esposti per costruzione: ```bash # solo decodifica: bc1p… → ESPOSTO per costruzione; gli hash → non determinabile nodsig check --file lista.txt # → scrive check-results.txt, creato leggibile solo dal proprietario (0600): # il file elenca i TUOI indirizzi, trattalo come sensibile ``` Vale la pena farlo girare così, a mani vuote, prima di tutto il resto, magari su un singolo indirizzo passato direttamente (`nodsig check --stdout `, con `--stdout` che stampa a video invece di scrivere il file: per un indirizzo pubblico va bene, per i propri no). Senza artefatti lo strumento decide quel che la codifica da sola decide, risponde `UNDETERMINED` su tutto il resto, dice quale flag servirebbe per ogni capacità che gli manca, e stampa il perimetro di ciò che una risposta può significare. È il comportamento che conviene vedere da un programma prima di affidargli una domanda che conta. La risposta piena, agganciando i backend che hai. Le credenziali del nodo si passano con `--cookie-file`: nessun comando accetta un segreto sulla riga di comando, perché l'argv di un processo è leggibile da chiunque sulla macchina (`ps`, `pgrep`). `--csv` per l'elaborazione; `--stdout` solo per le fixture pubbliche o per il piping: ```bash nodsig check --file lista.txt \ --archive "$ARCHIVE" \ --index "$INDEX" --derived "$DERIVED" \ --rpc http://127.0.0.1:8332 --cookie-file "$COOKIE" \ --out check-results.txt --csv check-results.csv ``` **Verifica — la forma del rapporto.** In testa, una riga `#` per ogni capacità agganciata: dice quale artefatto ha risposto (per formato, non per percorso), fino a che altezza, e sotto quale impronta. È il perimetro dichiarato di ogni risposta, ed è anche ciò che rende il rapporto condivisibile: non descrive la macchina che l'ha prodotto. Un artefatto non ancora sigillato lo dichiara invece di tacere, e il nodo, che un sigillo non può averlo, si presenta con la sua altezza. Poi, per indirizzo: ``` : # EXPOSED (by construction) | EXPOSED (by reuse) # | PROTECTED until first spend | UNDETERMINED balance: sats # solo con --rpc; «… but empty: nothing at stake» se 0 history: received … spent … unspent … (up to height …) co-inputs: spent in … tx(s), with … outputs of … other lock(s) … ``` in coda, i `caveats` che lo script stampa da sé (il perimetro di OGNI risposta: esposizioni fuori catena invisibili, script P2SH/P2WSH ignoti fino alla spesa, mempool = chiave già rivelata). La risposta `EXPOSED (by reuse)` dice anche DOVE la chiave è comparsa (scriptSig / witness / dentro uno script rivelato — quest'ultimo è esposizione da cofirmatario, non per forza per mano tua), e — dall'archivio che registra l'altezza della prima apparizione — da QUANDO. Il testo delle risposte è in inglese come tutto il codice; qui sopra è glossato. La riga `co-inputs` porta SEMPRE con sé il caveat common-input/CoinJoin: adiacenza negli input è un suggerimento, mai una certezza. **Un rapporto vero.** Questo è l'output catturato su tre indirizzi pubblici e notissimi, con l'archivio, l'indice e i derivati agganciati e senza `--rpc` (niente riga `balance`: il saldo è l'unica cosa che chiede il nodo). Sono indirizzi da manuale, nel senso letterale: il vanity `3P14159…` e i due esempi degli standard BIP-173 e BIP-350. Il testo è riportato tale e quale, senza tagli: ``` # exposure: reveal-archive-v2 (confirmed blocks 1..957,301, sealed aacaf02d…9ced) # history: outpoint-derived-v2 (confirmed blocks 1..957,301, sealed 44689372…1c80) # co-inputs: outpoint-derived-v2 (confirmed blocks 1..957,301, sealed 44689372…1c80) 3P14159f73E4gFr7JterCCQh9QjiTjiZrG p2sh: EXPOSED (by reuse) script revealed by a spend (1 key inside, co-signer exposure counts), first seen at height 184,727 history: received 9× 0.06212337 BTC, spent 7× 0.06002337 BTC, unspent 2× 0.00210000 BTC (heights 183,082–521,596, index at 957,301) co-inputs: spent in 7 tx(s), co-spent with 87 output(s) under 31 other lock(s) — common-input HINT, not ownership proof (CoinJoin breaks the assumption) bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 p2wpkh: EXPOSED (by reuse) key seen in a scriptSig; key seen in a witness; seen inside a revealed script (co-signer exposure counts), first seen at height 215,929 history: received 92× 0.01407024 BTC, spent 92× 0.01407024 BTC, unspent 0× 0.00000000 BTC (heights 511,207–957,250, index at 957,301) co-inputs: spent in 80 tx(s), co-spent with 35 output(s) under 18 other lock(s) — common-input HINT, not ownership proof (CoinJoin breaks the assumption) bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0 p2tr: EXPOSED (by construction) taproot (bc1p…): the program IS the key history: received 14× 0.00105034 BTC, spent 14× 0.00105034 BTC, unspent 0× 0.00000000 BTC (heights 700,829–918,525, index at 957,301) co-inputs: spent in 5 tx(s), co-spent with 0 output(s) under 0 other lock(s) — common-input HINT, not ownership proof (CoinJoin breaks the assumption) caveats (the perimeter of every answer above): - off-chain exposure is invisible here: an xpub shared with a service exposes descendant keys without any on-chain trace; - a P2SH/P2WSH address hides its script until it spends: "protected" speaks of the hash, not of who could spend behind it; - perimeter is CONFIRMED blocks up to the stated heights: a spend sitting in the mempool has already revealed its keys. ``` Come si legge. I tre indirizzi mostrano tre strade diverse verso la stessa risposta. Il P2SH è esposto perché il suo script è stato rivelato spendendo: prima di quella spesa si sarebbe letto `PROTECTED`, e la riga dice quante chiavi lo script conteneva (qui una) e da quando sono in vista — quel conteggio arriva dalla spesa che ha rivelato lo script, non dall'hash, che da solo non dice nulla. Il P2WPKH è esposto tre volte, e la terza riga conta: la chiave è stata vista **dentro uno script rivelato**, cioè da un cofirmatario, e non per forza per mano di chi possiede questo indirizzo. Il Taproot non ha avuto bisogno di nessun archivio: la sua esposizione si legge dalla stringa, perché il programma È la chiave. Sono tre indirizzi pubblici e molto trafficati, e il rapporto lo dice senza giri di parole: chi controlla i propri lo troverà quasi sempre più corto e più noioso di questo. La prova sui tre è comunque l'unica che si possa mostrare per intero senza chiedere a nessuno di pubblicare i suoi indirizzi. Il tempo: ~2 secondi per i tre, con tutti i backend agganciati e gli artefatti su disco locale (via LAN erano 13: vale anche qui la regola del mount). ### Interrogare l'indice e i derivati Quattro comandi, e chiedono solo i file: nessuna rete, nessun nodo. ```bash nodsig index lookup --index "$INDEX" TXID:VOUT nodsig derived fee --index "$INDEX" --derived "$DERIVED" TXID nodsig derived cospends --index "$INDEX" --derived "$DERIVED" TXID nodsig derived history --index "$INDEX" --derived "$DERIVED" --lock HASH160 ``` `history` accetta in alternativa `--spk` con lo scriptPubKey grezzo in hex, e ne calcola lui l'impronta: è la strada comoda quando si parte da un indirizzo decodificato invece che da una serratura già in mano. Un `lookup` sui due output della prima transazione fra persone, nel blocco 170, con la catena letta dai soli file: ``` f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16:0 created height 170 (2009-01-12 03:30 UTC) value 10.00000000 BTC (1,000,000,000 sat) lock hash160(scriptPubKey) a8cd299d425fe2f9e3ebde46abc94201ce6f579b spent height 92,240 (2010-11-16 20:39 UTC) by ea44e97271691990157559d0bdd9959e02790c34db6c006d779e82fa5aee708e f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16:1 created height 170 (2009-01-12 03:30 UTC) value 40.00000000 BTC (4,000,000,000 sat) lock hash160(scriptPubKey) e01507f88b6dcc026c7062029c03adb11553de10 spent height 181 (2009-01-12 06:02 UTC) by a16f3ce4dd5deb92d98ef5cf8afeaf0775ebca408f708b2146c4fb42b41e14be ``` I 50 BTC dell'input escono divisi in 10 e 40, e la fee è zero: il resto torna sulla serratura del pagante, com'era prassi allora. È anche il modo più corto per controllare che l'indice risponda: sono numeri che chiunque può confrontare con un explorer. Un altro controllo con un valore noto fuori dai nostri file, la fee della transazione delle due pizze: ``` $ nodsig derived fee --index "$INDEX" --derived "$DERIVED" \ a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d: fee 99,000,000 sat (0.99000000 BTC) ``` La stessa transazione, chiesta a `cospends`, mostra a che cosa serve il terzo derivato: 131 output spesi nello stesso momento, 111 dei quali da 0,01 BTC esatti, e tutti e 131 sotto un'unica serratura. È la forma tipica di un portafoglio che raschia il fondo per mettere insieme una cifra. Vale la pena ripetere il caveat che il comando stampa da sé: l'adiacenza negli input è un suggerimento sulla proprietà comune, mai una prova, e il CoinJoin esiste apposta per romperla. Qui il suggerimento non serve neanche, perché la serratura è una sola: il caso in cui la co-spesa dice qualcosa è quello in cui le serrature sono diverse, ed è anche quello in cui può sbagliarsi. **Che cosa costa una domanda.** Sui nostri file, raggiunti via LAN: circa 11 secondi la prima lettura a freddo, ~7 secondi una storia corta, ~13 secondi il controllo di tre indirizzi coi tre backend locali. Col saldo acceso (`--rpc`) si passa al minuto, ma quel tempo è del nodo, non dei file: `scantxoutset` percorre l'intero UTXO set. C'è però un caso che costa molto di più, e conviene saperlo prima di incontrarlo: le serrature molto riusate. La serratura del blocco genesi ha 75.454 output per 57,22278291 BTC, e la sua storia impiega circa 18 minuti, perché per ogni riga la risalita all'altezza è un accesso a sé. Non è un errore nel risultato, è la forma dell'interrogazione: una serratura con decine di migliaia di eventi merita una risalita a blocchi, che oggi non c'è. ## Il disegno e i formati Qui sotto c'è il perché: come è pensata la passata del livello 3, come sono fatti i file che produce, e la mappa di tutti gli artefatti. Serve a chi vuole capire o reimplementare, non a chi si limita a eseguire i passi. Una cosa questo capitolo non fa: duplicare il repo. Le specifiche normative dei formati (`docs/formats/`), i contratti delle interfacce (`docs/contracts/`), l'architettura (`docs/ARCHITECTURE.md`) e la mappa completa di ogni file (`docs/ARTIFACTS.md`) vivono lì, accanto al codice che descrivono, e restano allineate anche quando il codice si muove. Qui restano le idee, le scelte con il loro perché, e l'esperienza della nostra esecuzione: quanto basta a capire tutto il flusso senza aprire il repo — che serve solo a chi vuole i byte. ### Le tre idee del livello 3 I passi si eseguono senza sapere nulla di questo; qui c'è il perché funzionano. Obiettivo: riprodurre in proprio la classe di stime «>34%» di BIP-361, cioè contare le monete correnti su serrature già aperte in passato. Non un campionamento statistico: un conteggio esatto a perimetro dichiarato, riproducibile bit per bit alla stessa altezza. Tre idee lo reggono. **Idea 1 — invertire il confronto.** Il modo ingenuo richiederebbe l'archivio di TUTTE le chiavi mai rivelate nella storia (miliardi di voci). Ma interessa solo quali delle serrature ATTUALMENTE nell'UTXO set siano già state aperte: si distilla dallo snapshot del livello 2 l'insieme degli hash delle serrature correnti «dietro hash» e si scandisce la storia controllando ogni rivelazione contro quell'insieme, conservando solo i colpiti. Memoria piccola, una passata sola. Vale la pena guardare quanto si restringe l'insieme, perché il numero che ne esce ricorre poi ovunque. Le voci P2PKH/P2WPKH/P2SH/P2WSH nell'UTXO set sono ~109M (si sommano dalla tabella del censimento), ma stanno dietro **51,7 milioni di serrature DISTINTE**: più monete sotto la stessa serratura sono il riuso visto dal lato dell'UTXO set, e all'insieme-bersaglio la serratura serve una volta sola. Distillate e ordinate, quelle 51,7M stanno in circa 1 GB, che si tiene in un buffer ordinato con ricerca binaria: è il `prepare` del passo 1. **Idea 2 — la serratura spesa si ricostruisce dai dati di sblocco, senza indici.** Un input riferisce la moneta spesa come (txid, posizione), non mostra la serratura; risalirvi con un lookup richiederebbe un indice della storia. Ma per i tipi standard i dati di sblocco contengono già ciò che serve a RICALCOLARLA: P2PKH → hash160 della pubkey nello scriptSig; P2WPKH → hash160 della pubkey nel witness; P2SH → hash160 del redeem script (ultimo push dello scriptSig); P2WSH → sha256 del witness script (ultimo elemento del witness). Basta leggere gli input. P2PK, P2TR e bare multisig qui non servono: già contati come esposti per costruzione. Alla spesa, però, il tipo non si risolve davvero: la scansione tratta l'ultimo push come **candidato** (redeem/witness script, o pubkey) e ne verifica l'hash contro le partizioni, senza dover sapere in anticipo che tipo fosse la moneta spesa. Il meccanismo del candidato — e il rumore atteso dei «malformed inner scripts», che ne è la conseguenza — è dettagliato in «Lo schedario incrementale». Attenzione alla direzione, perché è qui che l'intuizione ingenua inciampa: NON si cerca se la serratura «è comparsa» sulla catena. Comparire è il suo mestiere — l'hash è la destinazione dei pagamenti, compare ogni volta che riceve, e non rivela nulla. Quello che si cerca è se il suo SEGRETO sia mai stato mostrato: è alla spesa che il protocollo obbliga a esibire la preimmagine («ecco la chiave il cui hash è la serratura, ed ecco la firma»), e da quel momento la chiave è pubblica per sempre. La scansione percorre quindi i dati di sblocco, ricostruisce i candidati rivelati, li ri-hasha e chiede all'elenco: questa RIVELAZIONE apre una serratura che oggi custodisce ancora satoshi? Ogni sì accende un bit che non si spegne più. La costruzione della bitmap, in figura (il file delle serrature è la tabella; la bitmap è la sua colonna sì/no, staccata e appaiata per posizione): STORIA (blocchi in streaming) SNAPSHOT (i file del prepare) ogni input porta i suoi dati di sblocco locks_p2pkh.bin (ordinato) (scriptSig / witness) riga 0 digest | satoshi │ riga 1 digest | satoshi │ ricostruisci i candidati riga 2 digest | satoshi ▼ … (ricerca binaria + chiave · redeem · witness script indice dei primi 3 byte) │ ri-hash (hash160 / sha256) │ ▼ │ «questo digest è una riga?» ─────── lookup ────┘ │ │ sì, alla riga i ▼ hits_p2pkh.bin — una sequenza PIATTA di bit, uno per riga posizione 0 1 2 3 4 5 6 7 8 … bit 0 1 0 0 1 0 0 0 0 … → bit i = 1, per sempre (su disco impacchettati 8 per byte: bit i → byte i//8, pos. i%8; 21.267.062 righe p2pkh → 2.658.383 byte esatti) **Che cosa tiene la serratura, e come se ne legge il tipo.** Nell'UTXO non c'è la chiave: la serratura «dietro hash» tiene un *digest* — 20 byte (hash160) per P2PKH/P2SH/P2WPKH, 32 byte (sha256) per P2WSH — più la quantità. Il tipo NON si deduce dalla dimensione: si legge dal *template* di opcode dello scriptPubKey (P2PKH `76a914…88ac`, P2SH `a914…87`, P2WPKH `0014…`, P2WSH `0020…`). Che la misura non basti lo mostra il Taproot: `5120…`, 34 byte esatti come il P2WSH, diverso solo per l'opcode di versione (`51`/OP_1 contro `00`/OP_0). Ed è il template a dire insieme quale lunghezza di digest aspettarsi E quale funzione di hash usare sulla preimmagine rivelata. Nello schedario il tipo non è nemmeno scritto: è la partizione (un file per tipo) a codificarlo. **Cosa si confronta, esattamente — e i due livelli del caso «script».** Il confronto è sempre *digest contro digest*, mai forma contro forma: si ri-hasha la preimmagine esibita a una **spesa** (non ciò che compare a una ricezione) e si cerca quel digest tra le serrature ancora piene oggi. Confrontare il digest e non la forma è ciò che cattura le **facce**: la stessa chiave sotto `1…` (P2PKH) e `bc1q…` (P2WPKH) ha lo stesso hash160 — forme diverse, digest identico: cercando la forma si mancherebbero, cercando il digest si prendono entrambe. Per gli script, poi, i confronti sono DUE, e aprono serrature diverse: - **livello esterno**: il digest dello script rivelato (hash160 del redeem per P2SH, sha256 del witness per P2WSH) contro i lock P2SH/P2WSH — un match dice che *quella* serratura-script è stata aperta, e brucia la moneta dietro di essa; - **livello interno (cofirmatari)**: ogni pubkey ESTRATTA dallo script rivelato, ri-hashata a hash160, contro i lock single-sig — un match brucia la *faccia ordinaria* di quel cofirmatario (`1…`/`bc1q…`), anche se lui non ha mai speso di suo. È il criterio del perimetro «cofirmatari»: senza, si contano solo le serrature-script; con, anche le facce single-sig che quegli script espongono. **Un esempio coi byte veri (rifacibile a mano).** Una chiave pubblica reale e pubblica — quella della coinbase della genesi — e la sua faccia «a impronta»: pubkey (65 B) 04678afdb0…6bf11d5f hash160(pubkey) 62e907b15cbf27d5425399ebf6f0fb50ebb88f18 → lock P2PKH 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa L'hash160 della chiave È il digest scritto nel lock P2PKH. Ora la STESSA chiave dentro uno script (un 1-di-1 multisig — un solo cofirmatario, per tenere l'esempio a una chiave: `OP_1 OP_1 OP_CHECKMULTISIG`): redeem script 514104678afd…11d5f51ae hash160(script) a7086d18c683c4df2897a557b7446e2eb275f64a → lock P2SH 3GvCs3EGuPTH29CQY7Kuj1w6HUDRjsWg5Y sha256(script) 3e3baf2f87a3f725e48beb9484493be9bf5934af1f43715f10b310b0b767bbe9 → lock P2WSH bc1q8ca67tu850mjteytaw2ggjfmaxl4jd90raphzhcskvgtpdm8h05slrfwct Alla spesa che rivela questo script la scansione fa i due confronti: 1. ri-hasha lo script (hash160 → `a7086d18…`, o sha256 → `3e3baf2f…` per la versione witness) e lo cerca tra i lock P2SH/P2WSH: match → la moneta-script è bruciata; 2. estrae la pubkey e la ri-hasha (hash160 → `62e907b1…`), cercandola tra i lock single-sig: match → è bruciata anche la faccia `1A1zP1eP…`, benché su quell'indirizzo nessuno abbia speso. Un solo tasto reale, tre serrature diverse (P2PKH, P2SH, P2WSH), un criterio solo: *ri-hasha ciò che la spesa ha rivelato, cerca il digest tra le serrature correnti*. Ogni valore qui sopra si rifà con `RIPEMD160(SHA256(·))` per gli hash160 e `SHA256(·)` per il P2WSH — è la definizione operativa del «match» su cui poggiano tutti i conteggi del riuso. **Idea 3 — ogni scansione parziale è un limite inferiore valido.** Un indirizzo è bruciato dalla sua PRIMA spesa: ogni blocco letto può solo aggiungere bruciati, mai toglierne. Una run interrotta produce quindi un numero onesto («letto il 40% della storia, il riuso è ALMENO X BTC») che cresce monotonicamente verso il valore completo: il pavimento SI ALZA man mano che la scansione procede. La run si può spezzare per intervalli di altezze e riprendere; la curva (BTC bruciati vs quota di storia letta) è pubblicabile ed è essa stessa didattica. **Perimetro da dichiarare (le scelte, ciascuna attivabile nel codice):** - criterio base: serratura corrente il cui identico hash è già stato speso = «ha mai speso», il criterio di «Il tuo indirizzo è esposto?», a scala di catena; - estensione facce: ogni pubkey rivelata brucia anche le altre facce della stessa chiave (stesso hash160 per `1…`/`bc1q…`; la faccia incapsulata `3…` = hash160 dello script `0014`); - estensione cofirmatari: le pubkey interne ai redeem/witness script rivelati bruciano le rispettive facce single-sig; - esclusioni dichiarate (invisibili a QUALUNQUE scansione dei blocchi): chiavi viste solo in mempool e mai confermate; xpub fuori catena; P2SH/P2WSH mai spesi (ignoti). Il carattere «per difetto» del conteggio sopravvive anche al livello 3. **Architettura e riproducibilità:** identiche nello spirito al livello 2: il nodo estrae, il PC elabora. Blocchi raw via RPC sulla LAN (`getblockhash` + `getblock 0`, hex → byte), parser di blocchi/transazioni NOSTRO in Python (più impegnativo del parser dello snapshot, ma formato stabile e documentato; collaudo con blocchi sintetici da implementazione speculare + blocchi reali noti). Taglio alla STESSA altezza dello snapshot del livello 2 (oggi la 957.301): il file `utxos.dat` già in mano serve due volte, per l'insieme degli hash e, a valle, per sommare i BTC dei bruciati per tipo ed epoca con una variante del censimento. Stessa altezza + stesso perimetro = stessi byte per chiunque. Ordini di grandezza: ~680 GB di blocchi, ~3,5 miliardi di input; colli di bottiglia lettura sul Pi e parsing Python sul PC; stima ore-giorni, in tmux, senza assistenza. ### Le due strade e la run senza assistenza Obiettivo operativo: una run che, una volta avviata, gira da sola per giorni e sopravvive a interruzioni (di rete, del PC, del Pi) senza perdere lavoro né richiedere sorveglianza. La regola del manuale resta: il nodo estrae e basta, tutta l'elaborazione sta sul PC. Le due strade e il loro incontro, in figura: nodo Bitcoin (RPC via tunnel SSH) blocchi grezzi, integrità ricalcolata │ │ strada diretta │ │ strada dell'archivio (reuse) ▼ ▼ (archive) confronta subito ogni archivia OGNI rivelazione rivelazione coi locks, con la sua provenienza tiene solo i bit accesi (run ordinati su disco, (bitmap, ~6 MB) decine di GB) │ │ │ ▼ merge (fusione periodica) │ archivio fuso e canonico │ │ │ ▼ crosscheck: rilegge │ l'archivio CONTRO i locks e │ ricostruisce le bitmap per │ via indipendente ▼ ▼ impronta sha256 ══ devono coincidere ══ impronta sha256 (la verifica incrociata del livello 3) (--graph, opzionale su ENTRAMBE le strade: il grafo si co-emette a lato e non tocca né bitmap né archivio) La strada diretta ha i suoi controlli di coerenza, col perimetro detto giusto: `reuse stats` ricalcola l'impronta dalle bitmap su disco e la confronta con quella registrata nel checkpoint PRIMA di riportare qualunque numero — niente nodo, niente nuova scansione; se non torna, fallisce invece di produrre statistiche su bitmap che non corrispondono. E attesta che le bitmap dei colpi sono quelle, non che il set di serrature di partenza sia giusto: quello lo attestano i digest per tipo nel manifest dei lock. Le due cose insieme coprono l'intera catena del conteggio; una sola no. ### Lo schedario incrementale: appendibile per costruzione Idea di fondo: l'archivio delle rivelazioni non resta una scansione una-tantum ma nasce APPENDIBILE (run ordinati + watermark di altezza + fusione periodica), così a ogni blocco nuovo si elabora quel blocco, non si riscansiona la storia. Cresce in ALTEZZA (blocchi nuovi) e in LARGHEZZA (ogni domanda = un derivato con la sua regola di ricostruzione e la sua impronta). Non è «una mempool nostra»: solo blocchi confermati, di proposito (la mempool non è dato di consenso, sarebbe irriproducibile). **Trasporto ed efficienza.** Due strade per farsi consegnare i blocchi, e la scelta guarda i byte, mai la fiducia: l'integrità non dipende dal trasporto, perché ogni byte viene verificato per hash comunque (subito sotto). Via JSON-RPC le richieste vanno in *batch* (25-50 `getblock 0` per chiamata HTTP) per ammortizzare la latenza, ma i blocchi viaggiano in esadecimale: il doppio dei byte (~1,4 TB per ~680 GB di blocchi, sostenibile su LAN in ore). Via `--rest` (vedi i Prerequisiti) i blocchi arrivano binari, metà del traffico e nessuna credenziale sul filo, senza batching ma con `--prefetch-depth` a tenere più richieste in volo. Gli hash per altezza (`getblockhash`) si prendono comunque via RPC in batch all'inizio, una volta sola, fino all'altezza di taglio. Le due strade sono collaudate a produrre artefatti byte-identici. **Integrità senza fiducia nel trasporto.** Ogni blocco ricevuto viene verificato: l'hash dell'header ricalcolato (sha256d) deve coincidere con l'hash richiesto, e il `prevhash` di ogni header deve agganciare il precedente: la catena si autocertifica mentre la si legge, il tunnel non va creduto sulla parola. Precisazione importante, emersa dal collaudo del parser: header + Merkle delle txid NON coprono i byte del witness, perché il txid esclude il witness per costruzione (BIP 141), e proprio nel witness vive la maggior parte delle chiavi rivelate che il livello 3 conta. Il protocollo chiude il buco altrove: il *witness commitment* nella coinbase (un secondo Merkle, sui wtxid, impegnato in un output OP_RETURN marcato `aa21a9ed`). Il nostro parser verifica anche quello: ogni byte consegnato alla scansione, witness compreso, risale all'hash del blocco. Il primo abbozzo del parser non lo faceva, e il test di corruzione (un byte alterato nel witness passava pulito) lo ha smascherato: è il collaudo speculare che fa il suo mestiere. **Checkpoint e ripresa (il cuore del «senza assistenza»).** Stato su disco a ogni intervallo completato (es. ogni 10.000 blocchi): ultima altezza chiusa + insieme dei colpiti in file append-only + conteggi parziali. Al riavvio si riprende dall'ultimo checkpoint. La monotonia del limite inferiore rende ogni checkpoint un risultato già valido e pubblicabile («letta la storia fino all'altezza H, il riuso è almeno X»): la curva è fatta dei checkpoint stessi. Log con progresso, blocchi/s e stima d'arrivo; a fine run, riepilogo con le esclusioni di perimetro stampate (lo strumento dichiara da sé che cosa non vede). **I mestieri.** Il codice sta nel pacchetto `nodsig`, un modulo per mestiere. Qui i mestieri si elencano **per comando**, non per nome di file: i comandi sono superficie pubblica e non cambiano dentro una versione maggiore, mentre i nomi dei moduli interni non hanno quella protezione e si sono già spostati una volta. Chi vuole vedere come sono ripartiti oggi apre il repo, che è il posto dove quell'informazione resta vera. | Mestiere | Dove si vede | |---|---| | leggere blocchi e transazioni (header, legacy e segwit, scriptSig e witness) verificando ciò che si legge: hash dell'header, Merkle delle txid, witness commitment, nessun byte in coda | interno, sotto ogni comando che tocca la catena | | la strada diretta: distillare le serrature dallo snapshot, poi percorrere la catena tenendo solo le bitmap dei colpiti | `reuse prepare`, `reuse scan`, `reuse stats` | | la strada dell'archivio: registrare, fondere e sigillare, far incontrare le due strade, interrogare | `archive scan`, `archive merge`, `archive derive`, `archive crosscheck`, `archive lookup` | | rileggere il grafo co-emesso dalle passate con `--graph` | `graph fingerprint`, `graph stats`, `graph show` | | numerare la catena e interrogarla per outpoint | `index build`, `index verify`, `index stats`, `index lookup`, `index rewind` | | i tre file ordinati sopra l'indice, e le loro letture | `derived build`, `derived history`, `derived fee`, `derived cospends` | | l'aritmetica delle distribuzioni, condivisa, perché le statistiche significhino la stessa cosa ovunque | interno, sotto `reuse stats` e `curve deltas` | | i collaudi: catene e snapshot sintetici scritti da implementazioni speculari indipendenti, più blocchi reali noti come fixture pubbliche | `python3 -m pytest` | Una sola scelta di disegno va detta qui, perché spiega un numero che si vede nell'output e non un dettaglio interno: **l'estrazione lavora per candidati, non per classificazione**. Si raccoglie ogni rivelazione plausibile e decide l'insieme delle serrature, non un giudizio a monte sull'input. Un falso positivo richiederebbe una preimmagine di hash160, cioè non capita; un falso negativo finisce in un contatore dichiarato. Il limite inferiore, quindi, non si gonfia mai: al più resta più basso del vero. Il resto — sottocomandi, formati byte, invarianti, strategia di collaudo — è documentato nel repo, nei docstring dei moduli e sotto `docs/`. Quello è il testo canonico, e questo manuale non ne tiene una copia che invecchierebbe per conto suo. **Quanto costa, misurato sul canale vero.** Prima della run lunga abbiamo fatto un pilota sui primi 50.000 blocchi, per provare la meccanica e non i numeri: `prepare` sullo snapshot in 17 minuti (51,7M serrature dietro-hash: 21,3M p2pkh, 6,2M p2sh, 23,1M p2wpkh, 1,2M p2wsh), scan a ~380 blocchi/s via tunnel. Le due prove che contavano più della velocità: SIGKILL al processo vivo dopo il checkpoint 30.000 e ripresa, con **impronta finale identica alla run pulita**; e la prima verifica incrociata su dati veri, PASSED, stessa impronta `a00b230a…` raggiunta dalle due strade. Sulla catena intera, misure di sonda sul canale vero: pipeline fetch+parse ~6,7 MB/s via tunnel (fetch ~11 MB/s, parse con integrità completa ~16 MB/s, in serie); catena ~767 GB stimati da campioni per epoca (il `size_on_disk` del nodo dice 859 GB, ma include gli undo). Una passata è quindi ~32-36 ore di misura pura, realisticamente 1,5-2 giorni con estrazione e ricerche; le due che abbiamo fatto noi, 3-4 giorni, ~1,5 TB di traffico esadecimale ciascuna (la riesecuzione definitiva della strada dell'archivio, coi tre co-emessi, ha chiuso in 58 h 47). Il checkpoint ogni 10.000 blocchi limita la perdita da crash a ~40 minuti di lavoro, provato col SIGKILL del pilota. Uno strato di prefetch sovrappone fetch e parse e spinge il throughput verso il tetto del fetch; si spegne con `--no-prefetch`. > **La strada diretta (`reuse scan`), al taglio all'altezza 957.301.** > Riuso a perimetro completo (`faces=on, cosigners=on`): 8.784.364 > serrature riusate su 5.084.725,41 BTC (p2pkh 3.925.128 / 1.185.751,57; > p2sh 1.155.896 / 1.283.224,01; p2wpkh 3.612.318 / 1.924.332,48; > p2wsh 91.022 / 691.417,35). Impronta dei colpiti > `5fd579dbc6190f5bf08e35947ab77a55e067aa366acd13bd49fa30d3ba9d0ee7`. > `malformed scriptSigs: 0`; `malformed inner scripts: 2.918.749.560`. > Il numero grosso è l'estrazione a candidati che si dichiara: è la > rilettura come script dell'ultimo push di ogni spesa single-sig > ordinaria (una pubkey in P2PKH/P2WPKH, o una firma DER nelle vecchie > P2PK), che non parsa perché script non è. L'hash esterno del candidato > viene comunque calcolato e confrontato con le serrature, e il riuso > cresce solo su un match. Lo zero dei `malformed scriptSigs` dice che > ogni scriptSig si è scomposto in push regolari; il numero grosso vive un > livello più in dentro, dove per progetto si sovra-raccoglie. **Freschezza dei dati.** I numeri sono al taglio 957.301, e la data si dichiara come sempre: il valore del conteggio è metodologico e di ordine di grandezza, qualche settimana non sposta nulla di materiale. La freschezza futura non si compra rifacendo il giro, ma con gli aggiornamenti incrementali dello schedario: nuovo snapshot, passata unica in append dai blocchi nuovi, `derive` contro le serrature fresche. Chi vuole la prova più forte esegue questo manuale alla lettera alla stessa altezza 957.301 e riproduce AL BYTE le impronte pubblicate. ### Il grafo co-emesso La decisione: durante la run completa si emettono anche i record GREZZI del grafo delle transazioni — per ogni blocco, quali monete ogni transazione crea (importo + serratura) e quali monete consuma (i riferimenti agli output precedenti). La parte costosa della lettura (fetch + parse con integrità completa) è pagata comunque dalla scansione: l'emissione aggiunge solo scritture sequenziali locali; rifarla dopo costerebbe un'altra passata intera sulla catena. È la materia prima delle domande future (storia dei pagamenti di un indirizzo, clustering common-input, la sentinella sulle spese da porte-senza-chiave), che il modello di interrogazione di Electrs — uno scripthash alla volta, online, senza importi — non può servire. **Che cosa è, e che cosa NON è.** Non è un indice: i record restano i più fedeli possibile al blocco, in ordine di catena, trasformazione minima (un emettitore stupido ha una superficie di bug piccola). Ogni indice futuro (per indirizzo, per cluster, per classe di script) è un DERIVATO: si costruisce dai record con la sua regola di ricostruzione e la sua impronta, come da schedario (crescita in larghezza). Restano FUORI dal formato, con la ragione a verbale: scriptSig e witness (sono le RIVELAZIONI, casa loro è il reveal archive — il grafo è chi paga chi, sotto quale serratura); version/locktime/sequence (contabilità di consenso, non flusso); le fee (sono un JOIN di questi stessi record, conservarle sarebbe trasformazione, non fedeltà). Il primo derivato esiste già e fa da esempio dell'impianto in miniatura: `nodsig blockstats` distilla dal grafo una riga per blocco — transazioni, archi, tessere, satoshi messi in nuovi output — in un CSV ordinato per altezza, col suo contratto di replica nel `meta.json` (l'impronta del grafo di partenza, l'altezza, le righe): ricostruito dallo stesso grafo alla stessa altezza, è byte-identico. Con il confine onesto dichiarato: i satoshi sono movimento, non emissione né saldo, e le fee non ci sono — sono il join che l'indice degli outpoint paga una volta per tutte. **Le stesse discipline del resto.** Run che piastrellano intervalli di altezze + watermark nello stato + sha256 per file (il grafo non si crede sulla parola quando lo si rilegge, come i blocchi non si credevano al fetch); run orfani di un crash eliminati alla ripresa; IMPRONTA CANONICA = sha256 del flusso concatenato in ordine di altezza, cieca ai confini dei run (artefatti di buffering, non dati): run interrotta e ripresa = stessi byte della run unica, chiunque ri-emetta alla stessa altezza deve atterrare sulla stessa stringa (il gemello, per il grafo, di muhash e delle impronte di scanner e archivio). `graph fingerprint` rilegge tutto e fa anche da audit di integrità; `graph digest` fa lo stesso confronto SENZA riscrivere niente, intervallo per intervallo, ed è il modo in cui una riscansione controlla di emettere ancora gli stessi byte di un grafo che già esiste, senza pagarne di nuovo i ~300 GB. La spec normativa del formato — record, ordinamento, forma canonica — sta nel repo, `docs/formats/Graph-v2.md`. **Dove vive, e a che condizioni.** L'emissione è SPENTA di default (`--graph`): chi clona gli strumenti può rifare il conteggio parco senza pagare i ~300 GB. Il percorso è libero e il profilo di scrittura — append sequenziali grandi, file scritti tmp-poi-rename — è fatto apposta per reggere anche un volume di rete: il grafo nasce già accanto al suo backup. Implicazioni oneste: se il volume ha un singhiozzo la run muore e si riprende dal checkpoint (disciplina provata col SIGKILL del pilota); il `fingerprint` finale rilegge tutto, e a grandezza piena sono ore. ### Che cosa rende questi file «di tutti» I formati dei file sono documentati nelle sezioni sopra; qui c'è il criterio che li unifica, perché è il criterio (non i dettagli) che si porta via chi vuole costruire strumenti verificabili. **Dentro i byte non c'è nulla della nostra esecuzione.** Nei file binari (serrature, bitmap dei colpiti, run del grafo, run dell'archivio) non esiste un orario nostro, un nome di macchina, un ordine di scoperta: solo dati che la catena detta a chiunque (il `time` che compare nel grafo è quello dell'header del blocco, dichiarato dal miner, non l'ora della scansione). Tutto ciò che è biografia della run — quali file ha prodotto, con che sha256, quante transazioni ha attraversato — vive separato, nel diario di bordo `state.json`. La regola in una riga: I DATI APPARTENGONO ALLA CATENA, IL DIARIO DI BORDO A NOI. È anche la ragione di principio delle statistiche per-run del grafo (sopra ne è data quella operativa, il recupero pulito dal crash): un contatore globale dentro lo stato sarebbe funzione della storia delle interruzioni, cioè contaminerebbe con la biografia della run un archivio che deve dipendere solo dalla catena. **La garanzia di replicabilità vive sulla forma canonica, non sui singoli file.** I confini tra i file di run sono artefatti di lavorazione (cadenza dei checkpoint, buffer, interruzioni): chi replica può spezzare il lavoro come vuole e atterrare sugli stessi byte. Ogni famiglia dichiara la sua forma canonica, ed è lì che vive l'impronta: per il grafo la concatenazione dei run in ordine di altezza; per l'archivio delle rivelazioni i file fusi del `merge` (ordinati e deduplicati globalmente); per lo scanner le bitmap dei colpiti, che sono canoniche già come file perché l'ordine dei loro bit è l'ordine dei file delle serrature, a loro volta ordinati e improntati. Le cose deliberatamente NON replicabili (nomi e sha256 dei singoli run, statistiche) stanno nel diario di bordo, mai nei dati. **Il contratto di replica: tre cose da fissare.** Chi vuole riprodurre i nostri byte esatti deve fissare (1) l'altezza di scansione (`--end`); (2) l'altezza dello snapshot delle serrature: stesso `dumptxoutset` = stesso `txoutset_hash` = stessi file locks byte per byte; (3) il perimetro (i default dichiarati; i flag `--no-faces`/`--no-cosigners` cambiano solo le bitmap dello scanner — grafo e archivio non ne dipendono, perché il perimetro lo applicano in lettura). Fissate le tre, ogni impronta pubblicata è una stringa su cui chiunque deve atterrare. **Quanto è piccolo il risultato.** Vale la pena dirlo plasticamente: l'intera risposta della strada diretta — quali delle 51,7 milioni di serrature dello snapshot la storia ha rivelato — sta in circa 6 MB: un bit per serratura, nell'ordine dei file del `prepare`. L'impronta canonica pubblicata a ogni checkpoint è lo sha256 di quei 6 MB. **La bitmap vive di un join (e il tipo non ha bisogno di tabelle).** Due cose che la bitmap NON contiene, e dove stanno. Il TIPO di serratura non è scritto da nessuna parte perché è codificato nella partizione: le bitmap sono quattro, una per meccanismo, ciascuna appaiata al suo file di serrature — il conteggio per tipo è gratis per costruzione. Tutto il resto invece la bitmap non lo sa: il bit `i` dice solo «la riga `i`», e il significato nasce accostandolo alla riga `i` del file delle serrature (digest e satoshi). È una colonna booleana staccata dalla sua tabella, appaiata per POSIZIONE, non per chiave: per questo il checkpoint rifiuta di riprendere su file di serrature diversi da quelli con cui è nato — un join posizionale sulla tabella sbagliata non darebbe errori, darebbe spazzatura silenziosa — ed è per questo che l'impronta delle bitmap è citabile solo insieme agli sha256 dei locks nel manifest. Il pattern continua verso l'alto: il file delle serrature non ha l'altezza di creazione delle monete, quindi «bruciati per età» sarà un join col dato che ce l'ha (lo snapshot `utxos.dat`), come la storia sarà un join col grafo. La bitmap resta minimale di proposito: il risultato nudo e improntabile; ogni domanda più ricca è un accostamento di pezzi canonici, mai informazione infilata nel risultato. **Un incrocio gratis tra livello 2 e livello 3.** Il manifest delle serrature somma i satoshi delle quattro categorie dietro-hash: ~18.118.632 BTC. Il censimento del livello 2, per strada tutta sua, arriva alla stessa cifra: totale UTXO (20.053.839) meno esposti per costruzione (1.932.589) meno la polvere fuori perimetro. Due letture indipendenti dello stesso snapshot che si danno ragione: il delta 19×3,125 del livello 1-2 ha trovato il suo gemello. **Un solo pattern per tutto ciò che non sta in memoria: ordina a pezzi, poi fondi.** Il `prepare` accumula le serrature fino a un tetto (8 milioni di record), ordina il mazzetto in RAM, lo scarica in un file di run temporaneo, e ricomincia; alla fine fonde i mazzetti in streaming guardando solo la prima carta di ciascuno e pescando ogni volta la più piccola (`heapq.merge`: in memoria k record, non k file). E nel flusso fuso la deduplicazione è gratis: in una sequenza ordinata gli uguali arrivano adiacenti, basta il confronto col record precedente per sommare i satoshi della stessa serratura (più UTXO dietro lo stesso hash: il riuso visto dall'altro lato — l'unità giusta è la serratura, perché una rivelazione brucia tutto quel che c'è dietro). Lo sha256 del file finale si aggiorna mentre lo si scrive (l'impronta nasce col file, non da una rilettura), i temporanei si cancellano: erano impalcatura, e infatti il risultato è identico byte per byte qualunque sia il numero o il confine dei mazzetti. Lo stesso pattern regge l'archivio delle rivelazioni (run ordinati + fusione periodica) e la sua lettura (fuso + run successivi, fusi al volo): un'idea sola, riusata tre volte. Il prepare in figura, dall'ammasso al termine di confronto: snapshot UTXO a 957.301 (utxos.dat: ~166M monete) │ streaming, una moneta alla volta ▼ distilla i 4 tipi dietro-hash (p2pk / p2tr / altro: p2pkh · p2sh · p2wpkh · p2wsh scartati, già esposti │ per costruzione) │ accumula in RAM (tetto: 8M record) ▼ mazzetto pieno → sort in RAM → scarico su disco run_p2pkh_0.tmp · run_p2pkh_1.tmp · … (~7 per tipo, ciascuno ordinato al suo interno) │ ▼ fusione in streaming (heapq.merge: si guarda la prima carta di ogni mazzetto, si pesca la minore) flusso unico ordinato → gli uguali arrivano ADIACENTI → stessa serratura: satoshi sommati (dedupe look-behind) │ sha256 aggiornato mentre si scrive ▼ locks_p2pkh.bin … locks_p2wsh.bin + manifest.json (51,7M righe: digest | satoshi) (impronte, base_hash) i .tmp si cancellano: impalcatura che non lascia traccia **Da dove vengono le idee (l'onestà del pedigree).** Nessun mattone di questa architettura è stato inventato qui, ed è un pregio da dichiarare: sono tecniche classiche, collaudate da decenni, e il lettore che vuole approfondirle trova i puntatori nei Riferimenti in coda al manuale. L'ordina-a-pezzi-poi-fondi è l'external merge sort dei manuali (Knuth, anni '70, nato per i nastri magnetici; lo usano `sort(1)` di Unix e ogni database quando la RAM non basta). I run ordinati che si accumulano con fusione periodica sono, concettualmente, un LSM-tree fatto a mano (l'idea dietro LevelDB e RocksDB) — con la differenza che i file restano nudi e leggibili invece di vivere dentro un motore. Il checkpoint con watermark e ripresa è disciplina standard dello stream processing. Il log grezzo immutabile con gli indici come derivati ricostruibili è l'event sourcing (il grafo è il log, i derivati sono le viste). Le impronte su forma canonica sono content addressing, la stessa famiglia del muhash di Core che citiamo come gemello. Le due strade indipendenti che devono coincidere sono vecchie quanto la partita doppia. Nel dominio specifico, il parente più prossimo della co-emissione è BlockSci (parsare la catena una volta in una forma compatta per analisi batch); Electrs è il cugino che si è scelto di NON imitare, perché serve un altro modello di domanda. Quello che non si trovava pronto — e che ha guidato ogni scelta di composizione — è la funzione obiettivo: questi sistemi ottimizzano per servire interrogazioni in fretta, qui si ottimizza per la VERIFICABILITÀ DA TERZI. Da lì le cose che nei progetti citati non esistono: l'impronta a ogni checkpoint, la forma canonica cieca ai confini come requisito di formato, il perimetro spostato in lettura per un controllo incrociato esatto, il limite inferiore monotono come forma pubblicabile del risultato, i file nudi e la sola libreria standard dove leggibilità e assenza di dipendenze SONO l'argomentazione. Riprendere mattoni collaudati non è pigrizia: è una garanzia di qualità (si sta su spalle note, non su trovate nostre) e insieme una dichiarazione d'onestà su dove finisce la tradizione e dove comincia il progetto. ### I principi dei formati La strada scelta si può dire in una riga: FILE IN FORMATO NOSTRO CHE RIDUCONO AL MINIMO LA SOVRASTRUTTURA, CON L'EFFICIENZA COSTRUITA OLTRE IL DATO GREZZO, MAI DENTRO. In sintesi, le regole che la reggono: - **Il grezzo è la fonte di verità.** Minimo, fedele al blocco, append-only; ciò che si può riderivare non entra (la fee è un join, non un campo). Acquisizione ed elaborazione separate: la passata estrae e non interpreta, le domande si rifanno gratis sui file locali. - **Formati minimali, documentati, neutrali.** Ogni formato nasce con la sua spec e la sua forma canonica, byte order e primitive dichiarati; neutrale per costruzione (l'impronta canonica è il test di cross-implementazione); evolve per versioni (`graph-v2` accanto a `graph-v1`), mai per mutazione. - **L'ordine è il primo indice.** Un file ordinato sulla chiave giusta è già un indice; l'intelligenza sta oltre il record (join posizionali, bitmap accostate alle tabelle), non dentro; strumenti standard dove bastano (SQLite-first), formato nostro solo dove servono scala e canonicità insieme. - **Ogni domanda è un derivato.** Regola di ricostruzione dichiarata e impronta propria: i derivati si cancellano e si rifanno, il grezzo no. Il contratto di replica (altezza, perimetro, versione) viaggia con ogni numero: un numero senza contratto è un'opinione con troppe cifre decimali. - **L'efficienza si misura, non si stima.** Si compra solo dove cambia il fattibile (i salti di categoria: ore invece di giorni, GB invece di centinaia); il risparmio che costa leggibilità si rifiuta. Una passata in più si giustifica solo se aggiunge informazione che una sola passata non regge. Il giudizio onesto: è l'architettura classica (log immutabile più viste derivate) con una funzione obiettivo insolita, la verificabilità da terzi. I due punti da sorvegliare: il costo di cittadinanza dei formati nostri (nessun ecosistema li legge — accettabile solo finché i formati restano minimali, documentati e neutrali, altrimenti è lock-in verso noi stessi) e l'efficienza, che resta priorità terza dopo verificabilità e leggibilità. ### Gli artefatti: il flusso a colpo d'occhio Figura in ASCII. È la mappa unica su cui poggiano entrambe le tabelle. ``` ┌─ census ─────────────────► census_.csv (contesto: quanti coin, di che tipo) utxos.dat ──►│ (snapshot) └─ reuse prepare ──────────► locks_/ (le «serrature»: gli UTXO attuali) │ ┌─────────────────────────┴───────────────────────┐ STRADA DIRETTA │ STRADA DELL'ARCHIVIO │ reuse scan ────┴──► checkpoint/ archive scan ───┴──► utxo_reveal/ (magra, via RPC) ├ hits_.bin (via RPC, appendibile) ├ runs/…_keys.bin ├ state.json ├ runs/…_scripts20.bin └ curve.csv (la curva) ├ runs/…_scripts32.bin │ └ state.json │ merge ──► manifest.json │ (impronta canonica) └──────────────► crosscheck ◄────────────────┘ PASS / FAIL (solo con --graph) graph/ ──► index build ──► index/ ──► derived build ──► derived/ (storia di una serratura, fee, co-spese) ``` Due strade indipendenti (il conteggio del riuso e l'archivio delle rivelazioni) convergono nel `crosscheck`, che pretende lo stesso numero da entrambe. Nella figura i comandi sono scritti senza il prefisso `nodsig`, che è sottinteso. La stessa passata può co-emettere anche l'archivio degli header (`--headers`) e il censimento dei nonce (`--nonces`), che nella figura non compaiono: sono rami laterali dello `scan`, descritti al passo 2 del livello 3. **Che cosa tenere dopo, e che cosa si può cancellare.** Costruito tutto e tenuto tutto si sta intorno ai ~910 GB: grafo ~301, indice 248,5, derivati 190,5, archivio 97,7, più lo snapshot, le serrature e — se co-emesso — il censimento dei nonce (59,7). Ma gli artefatti che rispondono alle domande sono TRE — archivio, indice, derivati — e il grafo, che è il più grosso di tutti, non lo legge nessuna interrogazione: è la materia prima da cui l'indice è stato costruito, e finito quel lavoro resta lì. È quindi il risparmio singolo più grande a disposizione, al prezzo che ricostruire o riavvolgere l'indice vorrebbe dire ripercorrere la catena da capo. Conviene tenerlo a chi intende seguire la catena in avanti; chi aveva una domanda sola può liberarlo. Snapshot e serrature servono a `census`, `reuse prepare` e `archive derive`: presi i numeri, tornano utili solo insieme a uno snapshot nuovo, a un'altezza nuova. La mappa completa — ogni file, chi lo produce, chi lo legge, che cosa si può saltare a seconda della domanda — è `docs/ARTIFACTS.md` nel repo, che la tiene allineata al codice. **Impronte gemelle.** Il conteggio del riuso (`reuse-scan`), l'archivio delle rivelazioni (`reveal-archive`) e il grafo (`graph`) portano ciascuno la stessa idea di impronta canonica, parente del `muhash` del nodo: rifacendo il passo, chiunque ottiene gli stessi byte e la stessa impronta sulla propria macchina. **Le impronte canoniche pubblicate.** Sono i valori che un terzo confronta per sapere se ha rifatto gli stessi conti. Le prime tre pinnano l'INPUT (lo stato della catena, verificabile subito con un nodo, senza il nostro codice); le altre il RISULTATO del nostro conteggio (riproducibile con gli strumenti). È il contratto di replica a due livelli. | Impronta | Blocco | Prodotta da | Valore | |---|---|---|---| | blocco-base dello snapshot | 957.301 | `dumptxoutset` | `000000000000000000005f3f0d5434e16bb6ea806ecd140be91bf6a15562c077` | | `txoutset_hash` (contenuto del set) | 957.301 | `dumptxoutset` | `ec79aed99b8d22d7315c76488fe09975ef9258039ee5583c7e8c7b6dc8d4cc58` | | `muhash` (insieme del set) | 957.282 | `gettxoutsetinfo` | `38dd237f9f83e14d2e8fefa396871a29c9a6226fc7f1f89eda2d9279e314671b` | | serrature / curva del riuso | 957.301 | `reuse scan` / `archive derive` | `5fd579dbc6190f5bf08e35947ab77a55e067aa366acd13bd49fa30d3ba9d0ee7` | | archivio delle rivelazioni | 957.301 | `archive merge` | `aacaf02dca2fc5ba8532e54fa75159041fc99051efa68eb63e59bc9537369ced` | | grafo grezzo | 957.301 | `graph fingerprint` | `a014f787256e1831c90290e04c2adbcf1fe00cfc3f2d26bb668bff491aa54190` | | indice degli outpoint | 957.301 | `index verify` / `stats` | `338c6c48f6e6c806c6d0a494bb9ca5060adcb83167c0db45328d39b40b14a69d` | | derivati (storia, fee, co-spese) | 957.301 | `derived verify` / `stats` | `44689372f169a5c503bdf128a082c31fef767e35c77696e8e60843b42afa1c80` | Le ultime due nascono dalla prima, che ciascun manifest dichiara come padre accanto alla propria impronta: chi rifà il percorso deve ritrovare la catena intera grafo → indice → derivati, non solo l'ultimo anello. L'impronta delle serrature è di natura diversa dalle altre quattro — non il sigillo di un artefatto ma il risultato della scansione, ricalcolabile offline dalle bitmap (vedi il livello 3) — ed è il motivo per cui si cita insieme ai digest dei lock, mai da sola. Il `muhash` è al blocco 957.282 perché nasce dal conteggio dal vivo del livello 1, qualche blocco prima dello snapshot; tutto il resto è pinnato alla 957.301, l'altezza dello snapshot su cui poggia il conteggio. **Condivisibilità.** Ogni artefatto qui elencato deriva da dati **pubblici** della catena. Nessuno contiene indirizzi propri: sono tutti condivisibili. Il census, in più, è aggregato per costruzione. ## Repo GitHub pubblico Il codice degli strumenti (parser UTXO, controllo degli indirizzi, indice e derivati) sta qui, sotto licenza MIT: **[github.com/amenano/nodsig](https://github.com/amenano/nodsig)** Il repo porta il controllo e tutto il codice per rieseguire l'analisi da capo; NON gli artefatti scaricabili (archivio, grafo, locks): sono grossi e chi vuole li ricostruisce con gli strumenti del repo — le impronte pubblicate dicono se la ricostruzione è quella giusta. Da un clone non c'è nulla da installare, e serve una cosa sola: **Python 3.10 o successivo**. Nessuna dipendenza, la libreria standard è tutto il runtime — che è il punto, non un vezzo: quello che si esegue è quello che si può leggere. ```bash git clone https://github.com/amenano/nodsig.git cd nodsig python3 -m nodsig --version python3 -m nodsig # la mappa dei comandi ``` `nodsig …` e `python3 -m nodsig …` sono lo stesso programma, e il manuale scrive sempre la prima forma; chi preferisce il comando sul PATH lo installa dal clone con `pip install .`. `python3 -m nodsig.` esegue un singolo strumento, ed è il modo di leggere il codice mentre gira; `nodsig -h` stampa le opzioni di ciascuno. Il README del repo dichiara anche il perimetro con cui il progetto si presenta, e vale la pena leggerlo prima di prendere per buona una risposta: è un proof of concept, senza roadmap né manutenzione promessa, e la regola che applica a sé stesso — un numero pubblicabile è un numero a cui sono arrivate due strade indipendenti — non si ferma al confine del repo. Un solo responso da un solo strumento è una pista, non un fatto, e questo strumento non fa eccezione a un principio su cui è costruito. Due pagine del repo meritano una segnalazione a parte, perché rispondono alle due domande che chi arriva fin qui si fa per prime. `docs/why-artifacts.md` prende sul serio l'obiezione «ho già un nodo, perché pagare il disco due volte»: la risposta, in breve, è che l'obiezione misura i byte e non il lavoro — il nodo archivia la catena nell'ordine in cui è successa, e ogni domanda di questo manuale è una domanda in un altro ordine; con i conti di quanto poco basta tenere davvero (per gli indirizzi a chiave singola, una sola partizione dell'archivio; per un Taproot, nessun file). E `docs/exposure-check.md` percorre la domanda dell'esposizione da sola, fino alla procedura sulla macchina senza rete. **Questo manuale descrive `nodsig` 1.x**, e la versione la dice il comando stesso (`nodsig --version`). I FORMATI sono il contratto, i comandi lo strumento comodo per ottenerli. Alla domanda «quale codice ha prodotto questo artefatto?» risponde l'artefatto stesso, non una promessa di processo: il manifest registra il produttore — versione, commit quando è determinabile, e un flag `dirty` che dice se l'albero portava modifiche mai committate. È la distinzione che una convenzione non può garantire: una regola si viola in silenzio, un campo registrato no. Il campo è dichiarato, non attestato — l'impronta resta una funzione dei soli byte, e il produttore non vi entra: si legge, non si verifica. ## Registro delle esecuzioni Le impronte non si ripetono qui: stanno per esteso in «Le impronte canoniche pubblicate», e questo registro serve a dire *quando* e *che cosa è successo*. | data | altezza | txouts | BTC totali | esito | |------|---------|--------|-----------|------| | 2026-07-09 | 957.282 | 166.238.015 | 20.053.779,26 | livello 1 | | 2026-07-09 | 957.301 | 166.224.061 | 20.053.838,64 | livello 2 CHIUSO | Della prima: nodo Umbrel non-pruned confermato, e coerenza con l'emissione teorica verificata (i −230 BTC noti). Della seconda: `base_hash` `…5562c077`, censimento in 10 minuti su PC, delta contro il livello 1 di 59,375 BTC = 19×3,125 esatti, ed esposte per costruzione 1.932.588,84 BTC (9,64%). La strada diretta del riuso (`reuse scan`) è stata percorsa una volta, in ~2 giorni: 8.784.364 serrature su 5.084.725,41330985 BTC, impronta `5fd579db…0ee7`, `malformed scriptSigs: 0`; e il `crosscheck` che la confronta con l'archivio ha chiuso PASSED (`real 258m`), le due strade sulla stessa impronta. È il controllo di metodo che si eredita, non si ripete. Le altre esecuzioni lunghe si registrano per durata invece che per totali, e stanno tutte alla stessa altezza 957.301. Le durate sono l'orologio a muro del registro, da avvio a esito, passata di sigillo compresa: è il motivo per cui l'indice qui segna 24 h 51 e nel suo passo 23 h 24 — là c'è il `build.seconds` del manifest, che conta la sola costruzione. Nessuno dei due numeri sbaglia; sono due domande diverse. | conclusa il | passo | durata | esito | |---|---|---|---| | 03/08 06:37 | `archive scan --graph --headers` (più `--nonces` co-emesso) | 58 h 47 | 7.704.658.388 rivelazioni in 2.787 run, `malformed scriptSigs: 0` | | 03/08 07:33 | confronto del grafo (digest in-scan) | dentro la scansione | 1.178/1.178 intervalli, `result: ok` | | 03/08 13:12 | `archive derive` (sui run) | 5 h 39 | tabella del riuso, impronta `5fd579db…0ee7`; curva a 96 righe | | 03/08 17:29 | `archive merge` | 4 h 17 | sigillo, impronta `aacaf02d…9ced` | | 03/08 20:11 | `nonces merge` (co-emesso) | 2 h 41 | sigillo del censimento dei nonce | | 03/08 20:11 | `headers fingerprint` | 13 s | impronta `6af1fed6…e5d4` | | 03/08 21:25 | `archive v1-digests` | 1 h 13 | i tre digest di luglio ritrovati identici | | 04/08 05:03 | `archive derive` (base fusa) | 4 h 56 | stessa `5fd579db…0ee7`, curva identica byte per byte | | 04/08 06:46 | `archive verify --deep` | 1 h 41 | PASSED | | 04/08 23:09 | `index build` | 24 h 51 | impronta `338c6c48…a69d`; `overwritten txids: 2, duplicate spends: 0, unresolved: 0` | | 05/08 00:15 | `index verify --graph` | 1 h 03 | ogni byte, scale ricostruite, genitore `a014f787…4190` confermato | | 05/08 01:14 | `headers crosscheck --index` | 59 min | 957.301 radici di Merkle ricalcolate, PASSED | | 06/08 07:39 | `derived build` | 14 h 30 | impronta `44689372…1c80`, identità incrociata fee/spesi | | 06/08 08:22 | `derived verify --index` | 42 min | ogni byte, genitore confermato | La tabella del riuso è uscita due volte con la stessa impronta, da due stati del codice: sui run il 03/08 e sulla base fusa il 04/08, dopo la correzione della curva a tasselli. La seconda riga d'intestazione differisce solo nel conteggio delle presenze in perimetro (i run si sovrappongono, la base fusa no): la tabella e l'impronta no. Più le misure già prese il 30/07 sul materiale vero: `census` ~18 min, `reuse prepare` ~35 min / ~1,4 GB. ## Riferimenti — gli algoritmi e le idee riprese Dichiarare da dove vengono i mattoni è una forma di onestà verso il lettore e insieme una garanzia: il progetto sta su tecniche collaudate da decenni, non su trovate estemporanee. Qui i puntatori per chi vuole studiarle alla fonte (vedi anche «Da dove vengono le idee»). - D. E. Knuth, *The Art of Computer Programming*, vol. 3: *Sorting and Searching*, 2ª ed., Addison-Wesley, 1998 — §5.4, «External Sorting»: l'ordina-a-pezzi-poi-fondi del `prepare` e dell'archivio, nato quando i dati vivevano sui nastri e la RAM era un lusso. - P. O'Neil, E. Cheng, D. Gawlick, E. O'Neil, «The Log-Structured Merge-Tree (LSM-Tree)», *Acta Informatica* 33(4), 1996 — run ordinati accumulati + fusione periodica: lo schedario è un LSM-tree fatto a mano, con file leggibili al posto del motore. - J. Kreps, «The Log: What every software engineer should know about real-time data's unifying abstraction», LinkedIn Engineering, 2013, https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying-abstraction — il log immutabile come verità e gli indici come derivati; è anche la cornice dei watermark di ripresa. - M. Kleppmann, *Designing Data-Intensive Applications*, O'Reilly, 2017 (e il talk «Turning the Database Inside-Out», Strange Loop 2014) — viste materializzate ricostruibili dal log: la cornice concettuale dei nostri «derivati con regola di ricostruzione e impronta propria». - M. Bellare, D. Micciancio, «A New Paradigm for Collision-Free Hashing: Incrementality at Reduced Cost», EUROCRYPT 1997 — hashing incrementale di insiemi: l'idea alla base del muhash di Bitcoin Core, il gemello di tutte le nostre impronte canoniche. - H. Kalodner, M. Möser, K. Lee, S. Goldfeder, M. Plattner, A. Chator, A. Narayanan, «BlockSci: Design and applications of a blockchain analysis platform», USENIX Security Symposium 2020 (arXiv:1709.02489) — il parente più prossimo della co-emissione del grafo: parsare la catena una volta in una forma compatta pensata per l'analisi batch. - Electrs, https://github.com/romanz/electrs — l'indicizzatore per-scripthash che si è scelto di NON imitare: utile proprio per capire il confine tra il suo modello di domanda (un indirizzo alla volta, online) e le domande globali che il grafo serve.