Modelli UTXO e basati su account · Lezione 1 di 6
Il modello UTXO per gli analisti
10 min di lettura
Bitcoin e le blockchain derivate da esso (Litecoin, Bitcoin Cash, Dogecoin e altre) non memorizzano saldi. Memorizzano output discreti, creati da una transazione e spesi da un'altra. Ogni saldo, flusso, tracciamento e cluster che un analista ricava da queste blockchain si basa su questa struttura; la maggior parte degli errori dei principianti (doppio conteggio, saldi fantasma, «pagamenti» che in realtà erano resto) deriva dal dimenticarsene.
Le monete sono output
Un output di transazione è un importo associato a uno script di blocco (lo scriptPubKey). È identificato dal suo outpoint: il txid della transazione che lo ha creato e la sua posizione nell'elenco degli output di quella transazione, scritto txid:vout. Un output ha solo due stati: non speso o speso; quando viene speso, lo è esattamente una volta e per intero. Non esiste la spesa parziale né un «conto» da cui attingere.
Un input non contiene un importo. Punta a un outpoint precedente e fornisce i dati (firma, chiave pubblica, script) che soddisfano lo script di blocco di quell'output. Per sapere quanto vale un input, si consulta l'output che spende (vedi la lezione Transazioni Bitcoin grezze).
Ne conseguono tre implicazioni per l'analisi:
| Ciò che vedono gli utenti | Ciò che registra la blockchain |
|---|---|
| «Il mio indirizzo contiene 1,2 BTC» | Diversi output non spesi i cui script corrispondono a quell'indirizzo, per un totale di 1,2 BTC |
| «Ho inviato 0,5 BTC» | Una transazione che ha speso output interi e ne ha creati di nuovi, di solito incluso il resto |
| «Il mio saldo è diminuito» | Gli output sono passati dall'insieme degli output non spesi a quello degli output spesi |
Il saldo di un indirizzo è quindi un valore derivato, mai memorizzato. Le nuove monete entrano tramite la transazione coinbase, che non ha input effettivi e paga il sussidio del blocco più le commissioni. Gli output coinbase possono essere spesi solo in un blocco situato almeno 100 blocchi dopo quello in cui sono stati creati: questa regola si chiama maturità coinbase.
Il grafo delle spese
Poiché ogni input punta a un solo output precedente, la blockchain forma un grafo delle spese. Ecco un piccolo esempio decodificato:
{
"txid": "b7e0…5d19",
"vin": [
{ "txid": "4f3a…c21e", "vout": 0 },
{ "txid": "9b1d…07aa", "vout": 1 }
],
"vout": [
{ "n": 0, "value": 1.00000000, "address": "3J9x…Qe1m" },
{ "n": 1, "value": 0.09950000, "address": "bc1q…r8tn" }
]
}
La transazione b7e0…5d19 spende l'output 0 di 4f3a…c21e (0,80 BTC) e l'output 1 di 9b1d…07aa (0,30 BTC). Gli input ammontano a 1,10 BTC; gli output a 1,0995 BTC; la commissione è la differenza, 0,0005 BTC. I due output sono ora non spesi e diventeranno archi solo quando qualcosa li spenderà.
Rappresentate come grafo, le transazioni sono nodi e gli output sono archi diretti dalla transazione che li ha creati a quella che li ha spesi. Gli output non spesi sono archi che non hanno ancora una destinazione. Il grafo è aciclico e punta sempre in avanti nel tempo, quindi è possibile seguire qualsiasi satoshi a ritroso, passo dopo passo, fino alle transazioni coinbase che lo hanno creato.
Una proprietà conta più di ogni altra: all'interno di una transazione, il protocollo non collega input specifici a output specifici. Quando entrano 0,80 e 0,30 BTC ed escono 1,00 e 0,0995 BTC, nulla sulla blockchain indica quale input abbia «pagato» quale output. Il valore viene aggregato e ridistribuito. Ogni metodo di tracciamento che assegna gli input agli output (first-in-first-out, haircut e così via) è una convenzione dell'analista, non un fatto ricavabile dai dati; la lezione Tracciare i fondi in avanti e all'indietro li confronta.
Output di resto
Poiché gli output vengono spesi per intero, di solito un pagamento invia una parte del valore al destinatario e restituisce il resto al mittente come resto. Nell'esempio precedente, uno dei due output è probabilmente resto, ma il protocollo non indica quale. Identificarlo è questione di euristiche (tipo di script, importi tondi, novità dell'indirizzo), trattate nella lezione Euristiche per individuare il resto.
Sbagliare a identificare il resto può avere effetti importanti. Trattarlo come un pagamento significa inventare una controparte e indirizzare il tracciamento lungo il ramo sbagliato. Considerare tutti gli output come valore trasferito gonfia il «volume» on-chain, perché il resto e i trasferimenti a sé stessi non spostano valore tra le parti. Le misure serie del volume cercano di escludere il resto e i consolidamenti; ciascuna di queste misure è valida solo quanto il modello del resto su cui si basa.
Riconoscere alcune strutture comuni delle transazioni è utile:
| Struttura | Schema tipico | Significato usuale |
|---|---|---|
| Pagamento semplice | 1–3 input, 2 output | Pagamento più resto |
| Consolidamento | Molti input, 1 output | Un wallet unisce output di piccolo importo, spesso quando le commissioni sono basse |
| Pagamento in batch | Pochi input, molti output | Un servizio paga molti clienti contemporaneamente |
| Passaggio di peeling | 1 input, 2 output: uno piccolo, uno grande | Parte di una catena di peeling; l'output più grande prosegue (vedi la lezione Metriche e schemi dei grafi) |
Età delle monete e dormienza
Ogni output registra il blocco in cui è stato creato, quindi ogni output non speso ha un'età. Quando un output viene speso, gli analisti calcolano i coin days destroyed (CDD): l'importo moltiplicato per il numero di giorni in cui è rimasto non speso. Un output da 2 BTC creato 400 giorni prima distrugge 800 coin days quando viene speso. Il CDD totale giornaliero diviso per i BTC spostati quel giorno dà una dormienza media: per quanto tempo, in media, le monete spostate erano rimaste inattive.
L'età è importante nelle indagini perché le monete rimaste inattive a lungo che si muovono all'improvviso sono un classico segnale di allarme. Gran parte dei bitcoin rubati a Bitfinex nel 2016 rimase nel wallet dell'hacker per anni, finché nel 2022 le autorità sequestrarono circa 94.000 BTC. Monitorare gli output collegati a un vecchio incidente in occasione della loro prima spesa è una delle regole più semplici e utili che un analista possa impostare.
Una suddivisione dell'insieme UTXO per età, a un'altezza fissa, si presenta così in SQL. Lo schema è tipico: una tabella outputs con una riga per ogni output e una tabella inputs in cui ogni riga indica l'outpoint speso.
-- Output non spesi all'altezza 900000, raggruppati per età (circa 144 blocchi al giorno)
WITH spent AS (
SELECT prev_txid, prev_vout
FROM inputs
WHERE block_height <= 900000
)
SELECT
CASE WHEN 900000 - o.block_height < 144 * 30 THEN '1: meno di 1 mese'
WHEN 900000 - o.block_height < 144 * 365 THEN '2: 1-12 mesi'
ELSE '3: oltre 1 anno' END AS age_band,
COUNT(*) AS utxos,
SUM(o.value_sat) / 100000000.0 AS btc
FROM outputs o
LEFT JOIN spent s
ON s.prev_txid = o.txid AND s.prev_vout = o.vout
WHERE o.block_height <= 900000
AND s.prev_txid IS NULL -- nessuna spesa all'altezza indicata o prima
GROUP BY 1
ORDER BY 1;
La query mantiene solo gli output creati all'altezza scelta o prima e senza una spesa corrispondente all'altezza scelta o prima: questa è la definizione dell'insieme UTXO in quel punto. Le fasce di età usano il numero di blocchi come approssimazione del tempo; per età precise, si esegue una join con la tabella blocks e si usano i timestamp dei blocchi (tenendo presenti le avvertenze sui timestamp nella lezione Blocchi e header come dati). Si noti il divisore 100000000.0: in alcuni motori, la divisione tra interi troncherebbe silenziosamente il risultato.
L'insieme UTXO
L'insieme di tutti gli output non spesi a un dato blocco è l'insieme UTXO. I nodi lo mantengono in un database dedicato (il chainstate), perché è ciò di cui hanno bisogno per convalidare nuove transazioni. È cresciuto notevolmente, anche a causa degli esperimenti con inscription e token che hanno creato molti output di piccolo importo; di conseguenza, le query che lo interessano sono pesanti.
Due metodi RPC di Bitcoin Core sono utili in questo caso:
gettxoutsetinforestituisce statistiche riassuntive sull'insieme, tra cuitxouts(il numero di output non spesi) etotal_amount(le monete che contengono). Per richiedere un'altezza storica è necessario l'indice opzionale coinstatsindex.scantxoutsetcerca nell'insieme UTXO attuale gli output che corrispondono a descrittori di output comeaddr(...)e restituisce ogni corrispondenza con txid, vout, importo e altezza. Considera solo gli output non spesi, mai la cronologia.
Bitcoin Core non mantiene un indice degli indirizzi, quindi per ottenere la cronologia completa delle transazioni di un indirizzo è necessario un indicizzatore o un data warehouse (vedi la lezione API dei nodi e accesso ai dati).
L'insieme fornisce anche un controllo di coerenza. Il suo total_amount dovrebbe essere vicino, ma inferiore, al sussidio totale mai emesso, perché una parte del valore è dimostrabilmente non spendibile e non entra mai nell'insieme: la ricompensa del blocco genesis, gli output OP_RETURN e le commissioni o i sussidi che i miner non hanno riscosso. Se il totale UTXO calcolato da una pipeline supera l'emissione, c'è un bug, in genere dovuto a spese mancanti o output duplicati.
Riutilizzo degli indirizzi
Gli indirizzi non sono conti. I wallet ben progettati ricavano un nuovo indirizzo per ogni ricezione da un seed deterministico gerarchico, e l'utente non ne vede la maggior parte. Una persona può controllare migliaia di indirizzi; l'indirizzo di un hot wallet di un exchange può interagire con milioni di controparti.
Il riutilizzo degli indirizzi (ricevere più volte allo stesso indirizzo) è comunque comune: indirizzi per le donazioni, alcuni indirizzi di deposito degli exchange, wallet datati e utenti poco attenti. Per un analista è un vantaggio e una trappola. Il riutilizzo collega i pagamenti senza bisogno di alcuna euristica e, una volta che un indirizzo ha speso, la sua chiave pubblica è visibile sulla blockchain. Tuttavia, le metriche a livello di indirizzo, come «indirizzi attivi» o «indirizzi con più di 1 BTC», misurano gli script, non le persone, e non dovrebbero mai essere presentate come conteggi di utenti.
Un dettaglio sottile: la stessa chiave pubblica può generare diversi indirizzi, uno per tipo di script (per esempio P2PKH legacy e P2WPKH SegWit nativo). Sembrano non correlati, ma quando la chiave viene rivelata in una spesa, un analista può collegarli proprio grazie alla chiave pubblica.
Focus sull'esame: un output è identificato dal suo outpoint (txid:vout) e viene speso una sola volta e per intero; gli input non contengono un importo, quindi i valori degli input e i saldi si ricavano dagli output stessi; il protocollo non collega input specifici a output specifici, quindi il tracciamento dagli input agli output è una convenzione; gli output di resto e il motivo per cui classificarli erroneamente distorce i tracciamenti e i volumi; la maturità coinbase di 100 blocchi e i coin days destroyed (importo × giorni non speso) e la dormienza come segnali di allarme; il calcolo dell'insieme UTXO a una certa altezza (creati all'altezza indicata o prima, non spesi all'altezza indicata o prima);gettxoutsetinfoescantxoutsete i loro limiti; il riutilizzo degli indirizzi e il motivo per cui il conteggio degli indirizzi non equivale al conteggio degli utenti.