Modèles UTXO et par compte · Leçon 1 sur 6
Le modèle UTXO pour les analystes
10 min de lecture
Bitcoin et les chaînes qui en sont dérivées (Litecoin, Bitcoin Cash, Dogecoin et autres) ne stockent aucun solde. Elles stockent des sorties distinctes, créées par une transaction et détruites par une autre. Tous les soldes, flux, traçages et regroupements qu’un analyste produit pour ces chaînes reposent sur cette structure, et la plupart des erreurs de débutant (double comptage, soldes fantômes, « paiements » qui étaient en réalité de la monnaie rendue) viennent du fait de l’oublier.
Les coins sont des sorties
Une sortie de transaction est un montant associé à un script de verrouillage (le scriptPubKey). Elle est identifiée par son outpoint : le txid de la transaction qui l’a créée et sa position dans la liste des sorties de cette transaction, sous la forme txid:vout. Une sortie n’a que deux états : non dépensée ou dépensée, et elle est dépensée une seule fois, en totalité. Il n’existe ni dépense partielle ni « compte » sur lequel puiser.
Une entrée ne contient pas de montant. Elle pointe vers un outpoint antérieur et fournit les données (signature, clé publique, script) qui satisfont le script de verrouillage de cette sortie. Pour savoir ce que vaut une entrée, il faut consulter la sortie qu’elle dépense (voir la leçon Transactions Bitcoin brutes).
L’analyse en tire trois conséquences :
| Ce que voient les utilisateurs | Ce qu’enregistre la chaîne |
|---|---|
| « Mon adresse contient 1,2 BTC » | Plusieurs sorties non dépensées dont les scripts se décodent en cette adresse, pour un total de 1,2 BTC |
| « J’ai envoyé 0,5 BTC » | Une transaction qui a consommé des sorties entières et en a créé de nouvelles, généralement avec de la monnaie rendue |
| « Mon solde a diminué » | Des sorties sont passées de l’ensemble des sorties non dépensées à celui des sorties dépensées |
Un solde d’adresse est donc un chiffre dérivé, jamais une donnée stockée. Les nouveaux coins entrent en circulation par la transaction coinbase, qui n’a pas de véritables entrées et verse la subvention de bloc ainsi que les frais. Les sorties coinbase ne peuvent être dépensées que dans un bloc situé au moins 100 blocs après celui qui les a créées : cette règle s’appelle la maturité coinbase.
Le graphe des dépenses
Puisque chaque entrée pointe vers exactement une sortie antérieure, la chaîne forme un graphe des dépenses. Voici un petit exemple décodé :
{
"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 transaction b7e0…5d19 dépense la sortie 0 de 4f3a…c21e (0,80 BTC) et la sortie 1 de 9b1d…07aa (0,30 BTC). Le total des entrées est de 1,10 BTC ; celui des sorties est de 1,0995 BTC ; les frais correspondent à la différence, soit 0,0005 BTC. Les deux sorties sont maintenant non dépensées et ne deviendront des arêtes que lorsqu’une transaction les dépensera.
Dans la représentation sous forme de graphe, les transactions sont des nœuds et les sorties sont des arêtes orientées, allant de la transaction qui les a créées à celle qui les a dépensées. Les sorties non dépensées sont des arêtes sans destination pour le moment. Le graphe est acyclique et pointe toujours vers l’avant dans le temps : on peut donc suivre n’importe quel satoshi en remontant, étape par étape, jusqu’aux transactions coinbase qui l’ont créé.
Une propriété compte plus que toute autre : au sein d’une transaction, le protocole ne relie pas des entrées précises à des sorties précises. Lorsque 0,80 et 0,30 BTC entrent et que 1,00 et 0,0995 BTC sortent, rien sur la chaîne n’indique quelle entrée a « financé » quelle sortie. La valeur est mise en commun puis redistribuée. Toute méthode de traçage qui attribue des entrées à des sorties (premier entré, premier sorti, méthode du haircut, etc.) relève d’une convention d’analyste, et non d’un fait lisible dans les données ; la leçon Traçage des fonds vers l’amont et vers l’aval les compare.
Sorties de monnaie rendue
Puisque les sorties sont dépensées dans leur intégralité, un paiement envoie généralement une partie de la valeur au destinataire et rend le reste à l’expéditeur sous forme de monnaie rendue. Dans l’exemple ci-dessus, l’une des deux sorties correspond probablement à de la monnaie rendue, mais le protocole n’indique pas laquelle. Son identification repose sur des heuristiques (type de script, montants arrondis, ancienneté de l’adresse), abordées dans la leçon Heuristiques de détection de la monnaie rendue.
Se tromper sur la monnaie rendue a de lourdes conséquences. La traiter comme un paiement revient à inventer une contrepartie et à envoyer le traçage sur la mauvaise branche. Comptabiliser toutes les sorties comme de la valeur transférée gonfle le « volume » sur la chaîne, car la monnaie rendue et les transferts entre ses propres adresses ne déplacent aucune valeur entre parties. Les mesures sérieuses du volume tentent d’exclure la monnaie rendue et les consolidations, et chacune de ces mesures dépend entièrement de la qualité de son modèle de détection de la monnaie rendue.
Reconnaître quelques formes courantes de transactions peut aider :
| Forme | Structure typique | Signification habituelle |
|---|---|---|
| Paiement simple | 1–3 entrées, 2 sorties | Paiement et monnaie rendue |
| Consolidation | De nombreuses entrées, 1 sortie | Un portefeuille regroupe de petites sorties, souvent lorsque les frais sont bas |
| Paiement groupé | Peu d’entrées, de nombreuses sorties | Un service paie de nombreux clients en une seule fois |
| Étape de pelage | 1 entrée, 2 sorties : l’une petite, l’autre grande | Partie d’une chaîne de pelage ; la grande sortie continue (voir la leçon Mesures et motifs des graphes) |
Âge des coins et dormance
Chaque sortie enregistre le bloc dans lequel elle a été créée ; toute sortie non dépensée a donc un âge. Lorsqu’une sortie est dépensée, les analystes calculent les coin-days détruits (CDD) : le montant multiplié par le nombre de jours pendant lesquels la sortie est restée non dépensée. Une sortie de 2 BTC créée 400 jours plus tôt détruit 800 coin-days lorsqu’elle est dépensée. Le CDD total d’une journée divisé par le nombre de BTC déplacés ce jour-là donne une dormance moyenne : la durée pendant laquelle les coins déplacés étaient restés immobiles, en moyenne.
L’âge est important dans les enquêtes, car les coins restés longtemps dormants qui se déplacent soudainement constituent un signal d’alerte classique. Une grande partie des bitcoins volés à Bitfinex en 2016 est restée dans le portefeuille du pirate pendant des années, jusqu’à ce que les autorités saisissent environ 94 000 BTC en 2022. Surveiller les sorties liées à un ancien incident pour détecter leur première dépense est l’une des règles les plus simples et les plus utiles qu’un analyste puisse mettre en place.
Voici en SQL une ventilation de l’ensemble UTXO par âge, à une hauteur fixe. Le schéma est courant : une table outputs avec une ligne par sortie, et une table inputs dans laquelle chaque ligne indique l’outpoint dépensé.
-- Sorties non dépensées à la hauteur 900000, regroupées par âge (environ 144 blocs par jour)
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: moins d’un mois'
WHEN 900000 - o.block_height < 144 * 365 THEN '2: 1 à 12 mois'
ELSE '3: plus d’un an' 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 -- aucune dépense à cette hauteur ou avant
GROUP BY 1
ORDER BY 1;
La requête ne conserve que les sorties créées à la hauteur choisie ou avant, sans dépense correspondante à cette hauteur ou avant : c’est la définition de l’ensemble UTXO à ce point. Les tranches d’âge utilisent le nombre de blocs comme approximation du temps ; pour obtenir des âges précis, faites une jointure avec la table des blocs et utilisez les horodatages des blocs (en tenant compte des mises en garde relatives aux horodatages dans la leçon Les blocs et les en-têtes comme données). Notez le diviseur 100000000.0 : dans certains moteurs, une division entière tronquerait silencieusement le résultat.
L’ensemble UTXO
L’ensemble de toutes les sorties non dépensées à un bloc donné est l’ensemble UTXO. Les nœuds le conservent dans une base de données dédiée (chainstate), car c’est ce dont ils ont besoin pour valider les nouvelles transactions. Il a pris beaucoup d’ampleur, notamment à cause des expérimentations liées aux inscriptions et aux jetons, qui ont créé de nombreuses petites sorties ; les requêtes qui le parcourent sont donc lourdes.
Deux méthodes RPC de Bitcoin Core sont utiles ici :
gettxoutsetinforenvoie des statistiques récapitulatives sur l’ensemble, notammenttxouts(le nombre de sorties non dépensées) ettotal_amount(la quantité de coins qu’elles contiennent). Pour demander une hauteur historique, l’index facultatif coinstatsindex est nécessaire.scantxoutsetrecherche dans l’ensemble UTXO actuel les sorties correspondant à des descripteurs de sortie tels queaddr(...)et renvoie chaque correspondance avec son txid, son vout, son montant et sa hauteur. Cette méthode ne voit que les sorties non dépensées, jamais l’historique.
Bitcoin Core ne conserve pas d’index des adresses ; l’historique complet des transactions d’une adresse nécessite donc un indexeur ou un entrepôt de données (voir la leçon API de nœud et accès aux données).
L’ensemble fournit également un contrôle de cohérence. Son total_amount devrait être proche du total des subventions jamais émises, mais inférieur à celui-ci, car une partie de la valeur est manifestement impossible à dépenser et n’entre jamais dans l’ensemble : la récompense du bloc genesis, les sorties OP_RETURN, ainsi que les frais ou la subvention que les mineurs n’ont pas réclamés. Si le total UTXO calculé par un pipeline dépasse le montant émis, il comporte un bogue, généralement des dépenses manquantes ou des sorties dupliquées.
Réutilisation des adresses
Les adresses ne sont pas des comptes. Les portefeuilles bien conçus dérivent une nouvelle adresse pour chaque réception à partir d’une seed déterministe hiérarchique, et l’utilisateur n’en voit jamais la plupart. Une personne peut contrôler des milliers d’adresses ; une adresse de portefeuille chaud d’une plateforme d’échange peut être en relation avec des millions de contreparties.
La réutilisation d’adresse (recevoir plusieurs fois à la même adresse) reste fréquente : adresses de dons, certaines adresses de dépôt de plateformes d’échange, anciens portefeuilles et utilisateurs négligents. Pour un analyste, c’est à la fois une aubaine et un piège. La réutilisation relie des paiements sans aucune heuristique et, dès qu’une adresse a dépensé des fonds, sa clé publique est visible sur la chaîne. Mais les mesures au niveau des adresses, comme « adresses actives » ou « adresses détenant plus de 1 BTC », mesurent des scripts, pas des personnes, et ne doivent jamais être présentées comme un décompte d’utilisateurs.
Une subtilité : une même clé publique peut produire plusieurs adresses différentes, une par type de script (par exemple P2PKH classique et P2WPKH SegWit natif). Elles semblent sans rapport, mais dès que la clé est révélée lors d’une dépense, un analyste peut les relier grâce à la clé publique elle-même.
Points clés pour l’examen : une sortie est identifiée par son outpoint (txid:vout) et est dépensée une seule fois, en totalité ; les entrées ne contiennent pas de montant, les valeurs des entrées et les soldes sont donc dérivés des sorties elles-mêmes ; le protocole ne relie pas des entrées précises à des sorties précises, le traçage des entrées vers les sorties est donc une convention ; les sorties de monnaie rendue et les raisons pour lesquelles leur mauvaise classification fausse les traçages et le volume ; la maturité coinbase de 100 blocs, ainsi que les coin-days détruits (montant × jours sans dépense) et la dormance comme signaux d’alerte ; calculer l’ensemble UTXO à une hauteur donnée (créées à cette hauteur ou avant, non dépensées à cette hauteur ou avant) ;gettxoutsetinfoetscantxoutsetet leurs limites ; la réutilisation des adresses et les raisons pour lesquelles le nombre d’adresses ne correspond pas au nombre d’utilisateurs.
Questions d’entraînement
Vérifiez votre compréhension. Ces questions ne comptent pas pour l’examen.
Connectez-vous pour enregistrer votre progression et reprendre là où vous vous êtes arrêté. C’est gratuit. Se connecter