Lire les données brutes d’une blockchain · Leçon 3 sur 6
Transactions Bitcoin brutes
10 min de lecture
Les explorateurs présentent les transactions Bitcoin sous forme de listes bien ordonnées d’adresses et de montants, mais cette vue est un décodage d’octets bruts, et certains éléments affichés ne figurent pas du tout dans la transaction. Lire la structure brute permet de savoir ce qui est enregistré sur la chaîne et ce qui a été dérivé, pourquoi les frais ne peuvent pas être calculés à partir d’une transaction seule, et quels champs portent les empreintes dont se servent ensuite les heuristiques.
La sérialisation, champ par champ
Une transaction est transmise et stockée sous forme d’une chaîne d’octets. Depuis SegWit (BIP 141, activé en août 2017), il existe deux formes : la forme héritée et la forme étendue définie dans BIP 144, utilisée lorsqu’au moins une entrée contient des données de témoin.
| Champ | Taille | Remarques |
|---|---|---|
| version | 4 octets | Entier en little-endian, généralement 1 ou 2 |
| marqueur, indicateur | 2 octets | 00 01, présent uniquement dans la forme SegWit |
| nombre d’entrées | variable | Entier CompactSize |
| entrées | variable | Chacune : txid précédent (32), index de sortie (4), longueur et scriptSig, séquence (4) |
| nombre de sorties | variable | Entier CompactSize |
| sorties | variable | Chacune : valeur en satoshis (8), longueur et scriptPubKey |
| témoin | variable | Forme SegWit uniquement : pour chaque entrée, un nombre d’éléments et les éléments |
| locktime | 4 octets | Hauteur de bloc ou heure la plus précoce à laquelle la transaction est valide |
Les entiers sont en little-endian : l’octet de poids faible vient en premier. Un entier CompactSize occupe un octet pour les valeurs inférieures à 253 et, sinon, un octet préfixe (fd, fe ou ff) suivi de 2, 4 ou 8 octets. Voici une transaction SegWit avec une entrée et deux sorties, répartie en ses différents champs, avec les longues valeurs tronquées :
02000000 version 2
00 01 SegWit marker and flag
01 input count 1
ab09…1d7c previous txid, internal byte order (32 bytes)
01000000 previous output index 1
00 scriptSig length 0 (native SegWit input)
fdffffff sequence 0xfffffffd
02 output count 2
40420f0000000000 value 1,000,000 sat
16 0014 3c1e…9d02 script, 22 bytes: P2WPKH
0c6a070000000000 value 485,900 sat
16 0014 77b4…e051 script, 22 bytes: P2WPKH
02 47 3044…01 21 02a1…5f witness: 2 items, signature (71 bytes), public key (33 bytes)
00000000 locktime 0
Lisez la première valeur en inversant ses octets : 40 42 0f 00 … devient 0x0f4240, soit 1 000 000 satoshis (0,01 BTC). Notez ce qui manque : aucune adresse d’expéditeur, aucun montant d’entrée et aucuns frais.
Entrées et outpoints
Une entrée ne contient pas de pièces ; elle pointe vers une sortie antérieure, appelée outpoint, notée txid:vout. Le txid de 32 octets dans l’entrée est stocké en ordre interne des octets, l’inverse de l’ordre affiché par les explorateurs : ab09…1d7c dans les octets devient 7c1d…09ab à l’écran. Oublier cette inversion est une cause classique du message « transaction introuvable ».
Le montant et le script dépensé se trouvent dans la sortie précédente, et non dans l’entrée. Pour savoir qui a payé et quel montant, il faut rechercher chaque outpoint. Le scriptSig de l’entrée (pour les scripts hérités) ou son témoin (pour les scripts SegWit) fournit les données qui satisfont les conditions de la sortie précédente, généralement des signatures et des clés publiques.
Le champ sequence de 4 octets indique également une intention : les valeurs inférieures à 0xfffffffe indiquent la possibilité de remplacement selon BIP 125 et, avec 0xfffffffd comme valeur courante, constituent une empreinte de portefeuille. Depuis Bitcoin Core 28.0 (2024), les nœuds acceptent par défaut les remplacements, que la transaction d’origine ait ou non signalé cette possibilité. Aujourd’hui, cette valeur intéresse donc surtout les analystes comme empreinte. Les verrous temporels relatifs (BIP 68) utilisent le même champ. Une entrée coinbase, qui crée de nouvelles pièces, comporte un txid précédent composé uniquement de zéros et l’index 0xffffffff.
Version, locktime et empreintes de portefeuille
Les champs autour des entrées et des sorties peuvent sembler standardisés, mais ils contiennent des informations. La version est 1 ou 2 dans presque toutes les transactions ; la version 2 est nécessaire pour les verrous temporels relatifs, et les portefeuilles n’utilisent pas tous la même version. Le locktime est interprété comme une hauteur de bloc lorsqu’il est inférieur à 500 000 000 et comme un horodatage Unix dans le cas contraire ; zéro signifie qu’il n’y a aucune restriction. De nombreux portefeuilles le définissent sur la hauteur du bloc courant pour dissuader les mineurs de réécrire les blocs récents afin de récupérer les frais (pratique appelée « anti-fee-sniping »). Ainsi, un locktime de 880 213 dans une transaction confirmée au bloc 880 214 en dit long sur le logiciel qui l’a créée.
Les valeurs de version, de locktime et de sequence, ainsi que les types de script utilisés et l’ordre des sorties, constituent une empreinte de portefeuille. Une empreinte isolée ne prouve rien, mais une combinaison cohérente de ces éléments dans plusieurs transactions est l’un des signaux utilisés dans la leçon Heuristiques de détection du change pour distinguer un paiement du change.
Sorties et types de script
Chaque sortie contient une valeur en satoshis et un scriptPubKey, c’est-à-dire la condition à remplir pour la dépenser. Les scripts standard suivent des modèles fixes, de sorte que leur type peut être déterminé à partir des premiers octets :
| Type | Modèle de scriptPubKey | Longueur | Défini dans | Préfixe de l’adresse | type dans Bitcoin Core |
|---|---|---|---|---|---|
| P2PKH | 76 a9 14 {20 bytes} 88 ac | 25 | version d’origine | 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 (plus long) | witness_v0_scripthash |
| P2TR | 51 20 {32 bytes} | 34 | BIP 341 | bc1p | witness_v1_taproot |
| OP_RETURN | 6a … | variable | — | aucun | nulldata |
Dans P2PKH, 76 a9 14 … 88 ac correspond à OP_DUP OP_HASH160 {push 20 bytes} OP_EQUALVERIFY OP_CHECKSIG. Les sorties SegWit commencent par un opcode de version (00 pour la version 0, 51 pour la version 1, Taproot), suivi d’une instruction de poussée du programme. Une sortie OP_RETURN est manifestement non dépensable et transporte des données, pas de valeur.
Une adresse est un encodage d’un script standard, et non un champ de la transaction. Les décodeurs la dérivent ; il peut ne pas y avoir d’adresse pour un script non standard, et les explorateurs peuvent en afficher une de manière improvisée. Les encodages eux-mêmes sont abordés dans la leçon Types de script et formats d’adresse.
Données de témoin
SegWit déplace les signatures du scriptSig vers une section distincte appelée témoin. Le contenu du témoin dépend du type dépensé :
- P2WPKH : deux éléments, une signature DER avec un octet sighash (généralement 71 ou 72 octets) et une clé publique compressée de 33 octets.
- P2WSH : les éléments de validation, suivis du script de témoin lui-même, par exemple un script multisignature 2-sur-3. La politique de dépense devient visible uniquement lorsque la sortie est dépensée.
- P2TR, chemin par clé : une signature Schnorr de 64 octets (BIP 340), ou de 65 octets avec un type sighash non par défaut. Une dépense par chemin de script révèle plutôt un script (BIP 342) et un bloc de contrôle.
- SegWit encapsulé dans P2SH : le scriptSig ne contient qu’un court script de rachat, tel que
0014…, et les signatures se trouvent dans le témoin.
Les octets du témoin bénéficient d’une pondération réduite dans l’espace des blocs. BIP 141 définit le poids comme taille de base × 3 + taille totale, la taille de base excluant le marqueur, l’indicateur et le témoin. La taille virtuelle (vsize) correspond au poids ÷ 4, arrondi à l’entier supérieur, et les taux de frais sont exprimés par vbyte.
Une transaction décodée en JSON
getrawtransaction de Bitcoin Core, avec une verbosité de 2, renvoie la transaction ci-dessus décodée, ainsi que chaque sortie dépensée (prevout) et les frais, lorsque le nœud dispose des données d’annulation du bloc :
{
"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
}
Les tailles concordent : la forme de base fait 113 octets et la section témoin, marqueur et indicateur compris, en ajoute 109, soit un total de 222 octets. Le poids est de 113 × 3 + 222 = 561, et 561 ÷ 4 s’arrondit à une vsize de 141. Les montants sont exprimés en BTC sous forme de nombres JSON ; convertissez-les en satoshis entiers avant tout calcul, comme l’explique la leçon Qualité, normalisation et provenance des données. decoderawtransaction sans options renvoie la même structure sans prevout ni fee, car cette commande n’a pas accès aux sorties précédentes.
Calculer les frais à partir des données brutes
Les frais ne sont jamais inscrits dans la transaction. Ils correspondent à la différence entre la valeur des entrées et celle des sorties :
- Entrées : un outpoint d’une valeur de 1 500 000 sat
- Sorties : 1 000 000 + 485 900 = 1 485 900 sat
- Frais : 1 500 000 − 1 485 900 = 14 100 sat, soit 14 100 ÷ 141 = 100 sat/vB
Une transaction coinbase n’a aucuns frais ; ses sorties réclament la subvention de bloc, plus les frais de toutes les autres transactions. Dans un entrepôt de données où les entrées et les sorties sont stockées dans des tables distinctes, le même calcul s’écrit ainsi (les noms des tables et des colonnes varient selon le jeu de données) :
-- Frais par transaction en satoshis : somme des sorties dépensées moins somme des nouvelles sorties
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 jointure de chaque entrée avec sa sortie précédente est l’étape que la transaction brute ne peut pas effectuer à elle seule. Les transactions coinbase sont exclues, car leurs entrées ne correspondent à aucune sortie antérieure.
txid et wtxid
Le txid est le double SHA-256 de la transaction sérialisée sans le marqueur, l’indicateur et le témoin. Le wtxid est le même hachage appliqué à la forme SegWit complète. Pour une transaction sans données de témoin, les deux sont identiques ; Bitcoin Core affiche le wtxid sous le nom hash.
L’exclusion du témoin du txid a résolu un problème de longue date : la malléabilité par un tiers. La modification de l’encodage d’une signature changeait auparavant le txid d’une transaction par ailleurs identique. Les modifications du témoin ne changent que le wtxid ; les chaînes de transactions non confirmées peuvent ainsi se référencer les unes les autres en toute sécurité. Les blocs s’engagent sur les wtxid au moyen d’un engagement de témoin dans la coinbase, où le wtxid de la coinbase elle-même est compté comme zéro. Dans les rapports et les recherches, identifiez les transactions par leur txid, dans l’ordre des octets affiché.
Points clés pour l’examen : l’ordre et la taille des champs de sérialisation, les entiers en little-endian et CompactSize ; les outpoints et l’ordre interne des octets inversé des txid ; les modèles de scriptPubKey de P2PKH, P2SH, P2WPKH, P2WSH et P2TR, avec leurs BIP et préfixes d’adresse ; le fait que les adresses sont dérivées et non stockées ; ce que révèlent les témoins pour chaque type de script ; le poids (base × 3 + total) et la vsize ; le calcul des frais à partir des sorties précédentes ; la distinction entre txid et wtxid, et la malléabilité.