Leggere i dati grezzi della blockchain · Lezione 3 di 6
Transazioni Bitcoin grezze
9 min di lettura
Gli explorer mostrano una transazione Bitcoin come elenchi ordinati di indirizzi e importi, ma quella visualizzazione è una decodifica di byte grezzi e alcune delle informazioni mostrate non sono affatto contenute nella transazione. Leggere la struttura grezza permette di capire cosa è registrato on-chain e cosa è stato derivato, perché la commissione non può essere calcolata dalla sola transazione e quali campi contengono le impronte su cui si basano le euristiche successive.
La serializzazione, campo per campo
Una transazione viene trasmessa e memorizzata come una stringa di byte. A partire da SegWit (BIP 141, attivato nell’agosto 2017), esistono due forme: la forma legacy e la forma estesa definita in BIP 144, usata quando almeno un input contiene dati witness.
| Campo | Dimensione | Note |
|---|---|---|
| version | 4 byte | Intero little-endian, di solito 1 o 2 |
| marker, flag | 2 byte | 00 01, presente solo nella forma SegWit |
| numero di input | variabile | Intero CompactSize |
| input | variabile | Ciascuno: txid precedente (32), indice dell’output (4), lunghezza di scriptSig e scriptSig, sequence (4) |
| numero di output | variabile | Intero CompactSize |
| output | variabile | Ciascuno: valore in satoshi (8), lunghezza di scriptPubKey e scriptPubKey |
| witness | variabile | Solo nella forma SegWit: per ogni input, un conteggio degli elementi e gli elementi stessi |
| locktime | 4 byte | Altezza o orario del blocco più vicino in cui la transazione è valida |
Gli interi sono little-endian: il byte meno significativo viene prima. Un intero CompactSize occupa un byte per i valori inferiori a 253; altrimenti, è composto da un byte prefisso (fd, fe o ff) seguito da 2, 4 o 8 byte. Ecco una transazione SegWit con un input e due output, suddivisa nei suoi campi e con i valori lunghi troncati:
02000000 versione 2
00 01 marker e flag SegWit
01 numero di input 1
ab09…1d7c txid precedente, ordine interno dei byte (32 byte)
01000000 indice dell’output precedente 1
00 lunghezza di scriptSig 0 (input SegWit nativo)
fdffffff sequence 0xfffffffd
02 numero di output 2
40420f0000000000 valore 1.000.000 sat
16 0014 3c1e…9d02 script, 22 byte: P2WPKH
0c6a070000000000 valore 485.900 sat
16 0014 77b4…e051 script, 22 byte: P2WPKH
02 47 3044…01 21 02a1…5f witness: 2 elementi, firma (71 byte), chiave pubblica (33 byte)
00000000 locktime 0
Leggi il primo valore invertendone i byte: 40 42 0f 00 … diventa 0x0f4240, cioè 1.000.000 satoshi (0,01 BTC). Nota cosa manca: nessun indirizzo del mittente, nessun importo dell’input e nessuna commissione.
Input e outpoint
Un input non contiene monete: punta a un output precedente, chiamato outpoint e scritto txid:vout. Il txid di 32 byte all’interno dell’input è memorizzato nell’ordine interno dei byte, che è l’inverso dell’ordine mostrato dagli explorer: ab09…1d7c nei byte diventa 7c1d…09ab sullo schermo. Dimenticare questa inversione è una causa classica dell’errore «transazione non trovata».
L’importo e lo script speso si trovano nell’output precedente, non nell’input. Per sapere chi ha pagato e quanto, devi cercare ciascun outpoint. Lo scriptSig dell’input (per gli script legacy) o il witness (per gli script SegWit) fornisce i dati che soddisfano le condizioni dell’output precedente, di solito firme e chiavi pubbliche.
Anche il campo sequence di 4 byte segnala un’intenzione: i valori inferiori a 0xfffffffe indicano la possibilità di sostituzione secondo BIP 125 e, con 0xfffffffd come scelta comune, costituiscono un’impronta del wallet. Da Bitcoin Core 28.0 (2024), i nodi accettano le sostituzioni per impostazione predefinita, indipendentemente dal fatto che la transazione originale lo segnalasse o meno; perciò oggi il valore interessa agli analisti soprattutto come impronta. I blocchi temporali relativi (BIP 68) usano lo stesso campo. Un input coinbase, che crea nuove monete, ha un txid precedente composto da tutti zeri e indice 0xffffffff.
Versione, locktime e impronte dei wallet
I campi intorno agli input e agli output sembrano elementi standard, ma contengono informazioni. La version è 1 o 2 in quasi tutte le transazioni; la versione 2 è necessaria per i blocchi temporali relativi e i wallet differiscono per quella utilizzata. Il locktime viene interpretato come altezza del blocco se è inferiore a 500.000.000, e come timestamp Unix altrimenti; zero significa nessuna restrizione. Molti wallet lo impostano all’altezza del blocco corrente per scoraggiare i miner dal riscrivere blocchi recenti allo scopo di incassare commissioni (pratica chiamata anti-fee-sniping). Un locktime pari a 880.213 in una transazione confermata all’altezza 880.214, quindi, dice qualcosa sul software che l’ha generata.
I valori di version, locktime e sequence, insieme ai tipi di script usati e all’ordine degli output, formano un’impronta del wallet. Una singola impronta non dimostra nulla, ma una combinazione coerente tra più transazioni è uno dei segnali usati nella lezione Euristiche per rilevare il resto per distinguere un pagamento dal resto.
Output e tipi di script
Ogni output contiene un valore in satoshi e uno scriptPubKey, cioè la condizione per spenderlo. Gli script standard seguono modelli fissi, quindi il tipo si può leggere dai primi byte:
| Tipo | Modello di scriptPubKey | Lunghezza | Definito in | L’indirizzo inizia con | type di Bitcoin Core |
|---|---|---|---|---|---|
| P2PKH | 76 a9 14 {20 bytes} 88 ac | 25 | originale | 1 | pubkeyhash |
| P2SH | a9 14 {20 bytes} 87 | 23 | BIP 16 | 3 | scripthash |
| P2WPKH | 00 14 {20 bytes} | 22 | BIP 141 | bc1q | witness_v0_keyhash |
| P2WSH | 00 20 {32 bytes} | 34 | BIP 141 | bc1q (più lungo) | witness_v0_scripthash |
| P2TR | 51 20 {32 bytes} | 34 | BIP 341 | bc1p | witness_v1_taproot |
| OP_RETURN | 6a … | varia | — | nessuno | nulldata |
In P2PKH, 76 a9 14 … 88 ac corrisponde a OP_DUP OP_HASH160 {push 20 bytes} OP_EQUALVERIFY OP_CHECKSIG. Gli output SegWit iniziano con un opcode di versione (00 per la versione 0, 51 per la versione 1, Taproot), seguito da un push del programma. Un output OP_RETURN è dimostrabilmente non spendibile e contiene dati, non valore.
Un indirizzo è una codifica di uno script standard, non un campo della transazione. I decoder lo derivano; uno script non standard potrebbe non avere alcun indirizzo e gli explorer potrebbero mostrare qualcosa di improvvisato. Le codifiche sono trattate nella lezione Tipi di script e formati degli indirizzi.
Dati witness
SegWit sposta le firme dallo scriptSig a una sezione witness separata. Il contenuto del witness dipende dal tipo di output che si sta spendendo:
- P2WPKH: due elementi, una firma DER con un byte sighash (di solito 71 o 72 byte) e una chiave pubblica compressa di 33 byte.
- P2WSH: gli elementi che soddisfano le condizioni, seguiti dallo script witness, per esempio uno script multisig 2-di-3. La politica di spesa diventa visibile solo quando l’output viene speso.
- P2TR, percorso della chiave: una singola firma Schnorr di 64 byte (BIP 340), oppure di 65 byte con un tipo sighash non predefinito. Una spesa tramite percorso dello script rivela invece uno script (BIP 342) e un blocco di controllo.
- SegWit avvolto in P2SH: lo scriptSig contiene solo un breve script di riscatto, come
0014…, mentre le firme si trovano nel witness.
I byte del witness hanno un peso ridotto nello spazio del blocco. BIP 141 definisce il peso come dimensione base × 3 + dimensione totale, dove la dimensione base esclude marker, flag e witness. La dimensione virtuale (vsize) è il peso ÷ 4, arrotondato per eccesso; le tariffe delle commissioni sono espresse per vbyte.
Una transazione decodificata in JSON
Il comando getrawtransaction di Bitcoin Core con verbosity 2 restituisce la transazione sopra decodificata, oltre a ciascun output speso (prevout) e alla commissione, se il nodo dispone dei dati di undo del blocco:
{
"txid": "4f3a…c21e", "hash": "9b07…e4d2",
"version": 2, "size": 222, "vsize": 141, "weight": 561, "locktime": 0,
"vin": [{
"txid": "7c1d…09ab", "vout": 1,
"scriptSig": {"asm": "", "hex": ""},
"txinwitness": ["3044…01", "02a1…5f"],
"prevout": {"value": 0.01500000, "height": 868120,
"scriptPubKey": {"type": "witness_v0_keyhash", "address": "bc1q…7k3f"}},
"sequence": 4294967293
}],
"vout": [
{"value": 0.01000000, "n": 0,
"scriptPubKey": {"type": "witness_v0_keyhash", "address": "bc1q…m2d8"}},
{"value": 0.00485900, "n": 1,
"scriptPubKey": {"type": "witness_v0_keyhash", "address": "bc1q…x9qa"}}
],
"fee": 0.00014100
}
Le dimensioni tornano: la forma base è di 113 byte e la sezione witness, contando marker e flag, aggiunge 109 byte, per un totale di 222 byte. Il peso è 113 × 3 + 222 = 561 e 561 ÷ 4, arrotondato per eccesso, dà una vsize di 141. Gli importi sono espressi in BTC come numeri JSON; convertili in satoshi interi prima di eseguire qualsiasi calcolo, come spiegato nella lezione Qualità, normalizzazione e provenienza dei dati. Il semplice decoderawtransaction restituisce la stessa struttura senza prevout e fee, perché non ha accesso agli output precedenti.
Calcolare la commissione dai dati grezzi
La commissione non è mai scritta nella transazione. È la differenza tra il valore degli input e quello degli output:
- Input: un outpoint del valore di 1.500.000 sat
- Output: 1.000.000 + 485.900 = 1.485.900 sat
- Commissione: 1.500.000 − 1.485.900 = 14.100 sat, quindi 14.100 ÷ 141 = 100 sat/vB
Una transazione coinbase non ha commissioni; i suoi output richiedono il sussidio del blocco più le commissioni di tutte le altre transazioni. In un data warehouse con tabelle separate per input e output, lo stesso calcolo si presenta così (i nomi delle tabelle e delle colonne variano a seconda del dataset):
-- Commissione per transazione in satoshi: somma degli output spesi meno somma dei nuovi output
WITH ins AS (
SELECT i.spending_txid AS txid, SUM(o.value_sat) AS in_sat
FROM inputs i
JOIN outputs o ON o.txid = i.prev_txid AND o.vout = i.prev_vout
GROUP BY i.spending_txid
), outs AS (
SELECT txid, SUM(value_sat) AS out_sat FROM outputs GROUP BY txid
)
SELECT ins.txid, ins.in_sat - outs.out_sat AS fee_sat
FROM ins JOIN outs ON outs.txid = ins.txid;
La corrispondenza tra ogni input e il suo output precedente è il passaggio che la transazione grezza non può eseguire da sola. Le transazioni coinbase vengono escluse perché i loro input non corrispondono ad alcun output precedente.
txid e wtxid
Il txid è il doppio SHA-256 della transazione serializzata senza marker, flag e witness. Il wtxid è lo stesso hash calcolato sulla forma SegWit completa. Per una transazione senza dati witness, i due sono identici; Bitcoin Core mostra il wtxid come hash.
Escludere il witness dal txid ha risolto un problema di lunga data: la malleabilità di terze parti. Modificare la codifica di una firma poteva cambiare il txid di una transazione altrimenti identica. Le modifiche al witness alterano solo il wtxid, quindi catene di transazioni non confermate possono fare riferimento l’una all’altra in sicurezza. I blocchi si impegnano sui wtxid attraverso un witness commitment nella coinbase, dove il wtxid della coinbase stessa vale zero. Nei report e nelle ricerche, identifica le transazioni usando il txid nell’ordine dei byte mostrato.
Focus sull'esame: l’ordine e la dimensione dei campi di serializzazione, gli interi little-endian e CompactSize; gli outpoint e l’ordine interno dei byte dei txid, invertito; i modelli di scriptPubKey di P2PKH, P2SH, P2WPKH, P2WSH e P2TR con i rispettivi BIP e prefissi degli indirizzi; il fatto che gli indirizzi sono derivati, non memorizzati; cosa rivelano i witness per ciascun tipo di script; peso (base × 3 + totale) e vsize; calcolo della commissione a partire dagli output precedenti; txid e wtxid e malleabilità.