L'anatomie d'une transaction Bitcoin

UTXO, structure binaire, langage Script, familles d'outputs, témoin et malléabilité : on démonte une vraie transaction Bitcoin de 222 octets, champ par champ, jusqu'à savoir la lire à l'œil nu.

sommaire · 7 sections

Dans l’article précédent , on a vu avec quoi Bitcoin signe : une courbe elliptique, un hash, une paire de clés. Restait la question d’après, celle qui compte vraiment : qu’est-ce qu’on signe, exactement ?

La réponse tient dans un objet plus petit qu’une photo de vacances. La transaction qu’on va démonter ici pèse 222 octets. Elle a été confirmée le 23 août 2026 dans le bloc 963 721, elle est publique, et à la fin de cet article tu sauras lire chacun de ses octets. On va y trouver le modèle UTXO, un mini-langage de programmation, quatre façons différentes de verrouiller la même somme, et une faille corrigée en 2017 qui a débloqué le Lightning Network.

Bitcoin ne connaît pas ton solde

Première chose à désapprendre : il n’existe nulle part, dans Bitcoin, une ligne « Nicolas : 1,45 BTC ». Pas de table des comptes, pas de champ « solde ». Ce que la blockchain contient, ce sont des sorties de transactions non dépensées — les fameux UTXO, pour Unspent Transaction Output.

L’analogie qui marche est celle du portefeuille physique. Tu n’as pas « 47 € » dans ta poche : tu as un billet de 20, un de 10, deux de 5, et de la ferraille. Quand tu paies 23 €, tu ne découpes pas un billet — tu tends le 20 et le 10, et on te rend 7 €. Un UTXO, c’est exactement ce billet : une somme indivisible, verrouillée par une condition, qui ne peut être dépensée qu’en entier.

Ce que ton wallet affiche comme « solde » est donc une addition qu’il fait lui-même, en scannant la chaîne pour repérer tous les UTXO qu’il sait déverrouiller. Le protocole, lui, ne fait jamais cette addition.

Modèle UTXO · Bitcoinon dépense des billets entiers0,40 BTC0,15 BTC0,90 BTC3 UTXO · solde affiché 1,45 BTCpas touchétransactionentrées 0,55 → sorties 0,54990,50 → Bobnouvel UTXO0,0499 → moila monnaie rendue0,0001 non attribué = frais du mineurles 2 UTXO d'entrée sont détruits2 nouveaux UTXO les remplacentaucun solde n'est « modifié » nulle partModèle compte · Ethereumon débite et on crédite un soldeavantAlice1,45Bob2,00transfert 0,50aprèsAlice0,95Bob2,50deux cases d'un registre globalsont réécrites
À gauche, le modèle UTXO de Bitcoin : on consomme des « billets » entiers et on se rend la monnaie à soi-même. À droite, le modèle de compte d’Ethereum : on réécrit deux cases d’un registre global.

L’alternative existe et elle est majoritaire ailleurs : c’est le modèle de compte, celui d’Ethereum. Là-bas, un transfert décrémente un champ et en incrémente un autre. C’est plus intuitif pour un humain, plus compact à stocker, et infiniment plus naturel pour un smart contract qui doit maintenir un état.

Alors pourquoi Satoshi a-t-il choisi l’autre voie ? Trois raisons tiennent encore aujourd’hui :

  • Validation parallélisable. Deux transactions qui touchent des UTXO différents sont totalement indépendantes : un nœud peut les vérifier en même temps, dans n’importe quel ordre. Avec des comptes, il faut sérialiser — sinon deux débits simultanés sur le même solde se marchent dessus.
  • Pas d’état mutable global. Vérifier une dépense, c’est répondre à une question locale (« cet UTXO existe-t-il, et la condition est-elle remplie ? »), pas interroger un registre que tout le monde réécrit en permanence.
  • Atomicité naturelle. Une transaction consomme ses entrées et crée ses sorties d’un seul tenant. Il n’existe pas d’état intermédiaire où l’argent aurait quitté A sans être arrivé chez B.

Le prix à payer est réel, lui aussi. Un wallet doit suivre chacun de ses UTXO individuellement, et les frais dépendent du nombre de billets que tu dépenses, pas du montant. Payer 10 € avec cinquante pièces de 20 centimes coûte plus cher que le payer avec un billet — d’où la pratique de la consolidation, où l’on regroupe ses petits UTXO pendant les périodes de frais bas. Et surtout : chaque paiement crée un UTXO de monnaie, qui revient chez toi et laisse une trace exploitable. On y reviendra dans Vie privée sur Bitcoin (BTC-A-07, à venir dans cette série).

222 octets, lus un par un

Assez de théorie. Voici la transaction, telle qu’elle circule sur le réseau — 444 caractères hexadécimaux, soit 222 octets :

02000000000101cfae86692a46f47926ed39137eef6477918cec8aa12860bdb2e12be57376c565
0100000000fdffffff02002e020000000000160014f3795ce71ad1ba973e90ed089830c0fe6664
852072d827000000000016001481562ca2cdf67e6906b71506c164e3118ee11cd2024730440220
4e1ff4e377943af919bc88c6d10fe46939856770dea1b6a25d763fd377b1ae67022058895a90f4
7b92140b9d96bad430d51e8ae564792b8185dedef75a1c84c4f55f012103e6332b27432156eb98
3facb9c478deffcaead4c337d53cedf408fe5b6f7322b36eb40e00

Ce n’est pas un exemple pédagogique fabriqué pour l’occasion : c’est la transaction b0d246e7…5353c72f, minée dans le bloc 963 721.1 Elle dépense un UTXO et en crée deux. Voilà comment la découper.

Une transaction Bitcoin réelle, octet par octettxid b0d246e7 ... 5353c72f · bloc 963 721 · 222 octets sur le filoffsettaillechampoctets (extrait)04 oversion02000000= 241 omarker SegWit0051 oflag SegWit0161 onb d'entrées (varint)01= 1 entrée732 otxid précédent (LE)cfae8669 ... 7376c565394 oindex de la sortie01000000= vout 1431 olongueur scriptSig00= vide444 onSequencefdffffffRBF + locktime481 onb de sorties (varint)02= 2 sorties498 ovaleur sortie 1002e020000000000142 848 sat571 olongueur scriptPubKey16= 22 o5822 oscriptPubKey 10014 f3795ce7 ... 64648520P2WPKH808 ovaleur sortie 272d82700000000002 611 314 sat881 olongueur scriptPubKey16= 22 o8922 oscriptPubKey 20014 81562ca2 ... 8ee11cd2P2WPKH1111 onb d'éléments du témoin0211272 osignature (1 o + 71 o)47 30440220 ... 4c4f55f001DER + SIGHASH18434 oclé publique (1 o + 33 o)21 03e6332b ... 6f7322b3compressée2184 onLockTime6eb40e00= 963 694ENTRÉESORTIESTÉMOINmarker + flag + témoin = les 109 octets que le txid ne voit jamais (SegWit)
La même transaction, champ par champ : offset, taille, contenu. Les trois blocs colorés isolent l’entrée, les sorties et le témoin — c’est la structure que tout nœud Bitcoin parse à la réception.

Quelques champs méritent un commentaire.

02000000 — la version. Quatre octets, en little-endian (l’octet de poids faible d’abord), donc la valeur 2 et non 33 554 432. Ce détail vaut d’être intégré une bonne fois : dans une transaction Bitcoin, tous les entiers sont en little-endian, y compris les identifiants de transaction, qu’on affiche pourtant à l’envers. C’est la source d’erreur numéro un de quiconque écrit son premier parseur.

00 01 — le marker et le flag. Deux octets qui n’existaient pas avant 2017. Le 00 occupe la place du nombre d’entrées : comme une transaction sans entrée est impossible, un vieux nœud qui lit 00 comprend « rien à voir ici » et s’arrête — c’est précisément le tour de passe-passe qui a permis d’ajouter le témoin sans hard-fork. Un nœud moderne, lui, lit le 01 derrière et sait qu’il a affaire à une transaction SegWit.

01 puis 02 — les compteurs. Ce sont des varint — des entiers à taille variable : en dessous de 253, le nombre tient sur un seul octet ; au-delà, un préfixe (fd, fe, ff) annonce 2, 4 ou 8 octets. Une transaction à 3 entrées et une transaction à 300 entrées ne consomment donc pas le même nombre d’octets pour dire combien elles en ont. C’est de l’optimisation d’octets, et Bitcoin en est plein.

cfae8669…7376c565 — la référence à l’UTXO dépensé. Trente-deux octets qui pointent vers la transaction d’où vient l’argent, suivis de quatre octets d’index (01000000 = la sortie n°1 de cette transaction). C’est la seule façon de désigner de l’argent dans Bitcoin : par son origine. Pas par une adresse, pas par un solde — par un couple (transaction, index).

00 — la longueur du scriptSig. Zéro. Le champ historique où l’on mettait la signature est vide, parce que cette transaction est une SegWit : la signature est partie plus loin, dans le témoin. On y arrive.

fdffffff — le nSequence. Un champ à l’histoire mouvementée. Aujourd’hui, sa valeur 0xfffffffd fait deux choses : elle active le nLockTime (n’importe quelle valeur inférieure à 0xffffffff suffit) et elle signale l’opt-in RBF (BIP125 ), qui autorise le remplacement de la transaction par une version mieux payée tant qu’elle n’est pas confirmée. Nuance : depuis Bitcoin Core 28 (octobre 2024), le remplacement est relayé par défaut, signal ou pas — le champ est resté, sa portée a changé.2

002e020000000000 — une valeur. Huit octets, little-endian encore, exprimés en Satoshi : 142 848 sat, soit 0,00142848 BTC. Il n’existe pas de nombre à virgule dans Bitcoin. Jamais. Le « BTC » est une convention d’affichage, rien de plus.

6eb40e00 — le nLockTime. 963 694, une hauteur de bloc — déjà dépassée au moment où la transaction est confirmée, donc sans effet bloquant. Ce n’est pas une erreur : Bitcoin Core règle par défaut le locktime sur la hauteur courante, une technique appelée anti-fee-sniping. L’idée : si un mineur était tenté de réorganiser le dernier bloc pour s’accaparer les transactions grasses, ces transactions-là ne seraient pas encore valides dans le bloc qu’il essaie de remplacer.

Et le total ? 142 848 + 2 611 314 = 2 754 162 sat en sorties, pour 2 754 952 sat en entrée. La différence — 790 satoshis — n’est écrite nulle part. Elle n’a pas besoin de l’être : dans Bitcoin, les frais sont ce qui reste. Ce que tu n’attribues à aucune sortie revient au mineur qui inclut la transaction. Un wallet buggé qui oublie sa sortie de monnaie fait donc cadeau du reste, et c’est déjà arrivé.

Script : le langage des verrous

Chaque sortie porte une condition de dépense. Cette condition n’est pas un type d’adresse ni un drapeau : c’est un petit programme, écrit dans un langage propre à Bitcoin, appelé Script.

Script est stack-based : il n’a pas de variables, seulement une pile. Chaque instruction — un opcode — dépile ses arguments et empile son résultat. Et surtout, il n’est pas Turing-complet : pas de boucle, pas de saut arrière, pas de récursion. Ce n’est pas un manque, c’est une décision de conception. Un script Bitcoin termine toujours, en un nombre d’étapes borné par sa propre longueur, ce qui rend le coût de sa validation prévisible avant même de l’exécuter. Ethereum a fait le choix inverse — la Turing-complétude — et a dû inventer le gas pour empêcher les boucles infinies de bloquer le réseau.

Le déverrouillage se fait en deux temps. La sortie qu’on veut dépenser porte un scriptPubKey (le verrou). La transaction qui la dépense fournit un scriptSig ou un témoin (la clé). On concatène les deux, on exécute, et si la pile finit sur une valeur vraie, la dépense est autorisée.

Voici le cas d’école, le P2PKH — la forme originelle de 2009 :

scriptPubKey   OP_DUP OP_HASH160 <empreinte 20 o> OP_EQUALVERIFY OP_CHECKSIG
scriptSig      <signature> <clé publique>
Exécution d'un script P2PKH — la pile, étape par étapescriptPubKey (le verrou) · OP_DUP OP_HASH160 <pkh> OP_EQUALVERIFY OP_CHECKSIGscriptSig (la clé) · <signature> <clé publique>1push depuis scriptSigsigpubkey2OP_DUPduplique le sommetsigpubkeypubkey3OP_HASH160hashe le sommetsigpubkeyH160(pk)4push <pkh>l'empreinte de l'adressesigpubkeyH160(pk)pkh cible5OP_EQUALVERIFYcompare et retiresigpubkeysinon : rejet6OP_CHECKSIGvérifie la signatureVRAIla pile finit sur une seule valeur vraie → la dépense est autoriséeun seul faux à n'importe quelle étape et la transaction est rejetéesommet de pile en haut
L’exécution d’un script P2PKH, pile visible à chaque étape. Six instructions, aucune boucle, un résultat booléen — c’est tout ce que Bitcoin demande pour autoriser une dépense.

Six opcodes suffisent à comprendre 90 % de ce qui circule :

OpcodeCe qu’il fait
OP_DUPduplique l’élément au sommet de la pile
OP_HASH160remplace le sommet par son RIPEMD-160(SHA-256(x))
OP_EQUALVERIFYcompare les deux éléments du sommet, les retire, échoue si différents
OP_CHECKSIGvérifie une signature contre une clé publique, empile vrai/faux
OP_RETURNrend la sortie définitivement indépensable (voir plus bas)
OP_IF / OP_ELSE / OP_ENDIFbranchement conditionnel — la seule structure de contrôle du langage

Deux remarques sur les limites. D’abord, Script ne voit rien du monde extérieur : ni la date réelle, ni un prix, ni le contenu d’une autre transaction. Il ne peut raisonner que sur ce qu’on lui empile et sur quelques métadonnées du bloc. Ensuite, une partie du langage a été désactivée en 2010 par Satoshi lui-même — dont OP_CAT, qui concaténait deux éléments — en raison de bugs et de risques de déni de service dans leur implémentation. Ces opcodes existent toujours dans le code, mais font échouer tout script qui les invoque. Leur éventuel retour est l’un des grands débats actuels, qu’on rouvrira en fin de série.

Quatre familles de verrous

La même somme peut être verrouillée de quatre façons différentes, empilées par quinze ans d’évolution du protocole. Chacune reste valide : Bitcoin n’a jamais retiré une famille en circulation.

Quatre façons de verrouiller la même sommescriptPubKey · préfixe d'adresse · taille réelle d'une transaction 1 entrée / 2 sortiesfamillescriptPubKey (le verrou posé sur la sortie)adressetailleP2PKH2009 · origineOP_DUP OP_HASH160 <20 o> OP_EQUALVERIFY OP_CHECKSIGle verrou complet est dans la sortie1...226 vBP2SH2012 · BIP16OP_HASH160 <20 o> OP_EQUALla sortie ne contient que l'empreinte du script3...166 vBP2WPKH2017 · BIP141OP_0 <20 o>signature sortie du txid, remisée en poidsbc1q...141 vBP2TR2021 · BIP341OP_1 <32 o>une clé publique tweakée, clé ou scriptbc1p...154 vBtailles mesurées sur des transactions réelles du bloc 963 721 (23 août 2026)Taproot n'est pas toujours plus léger : sa sortie pèse 12 o de plus, son entrée 41 unités de poids de moins
Les quatre familles d’outputs, leur scriptPubKey, leur préfixe d’adresse et leur coût réel. Les tailles sont mesurées sur quatre transactions réelles à 1 entrée et 2 sorties, prises dans le même bloc.

P2PKH (Pay to Public Key Hash, 2009) — le verrou complet est écrit dans la sortie : duplique, hashe, compare, vérifie la signature. Simple, lisible, et le plus cher de tous : 226 vB pour une transaction à une entrée et deux sorties. Les adresses commencent par 1.

P2SH (Pay to Script Hash, BIP16 , activé en avril 2012) — le renversement conceptuel. La sortie ne contient plus le script, seulement son empreinte. Le script réel, appelé redeem script, n’est révélé qu’au moment de la dépense, par celui qui dépense. Conséquence pratique énorme : c’est le payeur qui n’a plus rien à savoir. Envoyer à un multi-signature 2-parmi-3 devient aussi simple qu’envoyer à une adresse normale — la complexité est déplacée du côté de celui qui encaisse. Adresses en 3.

P2WPKH (SegWit v0, BIP141 , activé en août 2017) — la sortie se réduit à OP_0 <empreinte 20 o>, et la signature déménage dans le témoin. Le gain n’est pas cosmétique : les données du témoin sont facturées quatre fois moins cher que le reste (voir l’encadré). Adresses bc1q…, celles de notre transaction d’exemple. 141 vB.

P2TR (Taproot, BIP341 , activé en novembre 2021) — la sortie est OP_1 <clé publique 32 o>, et cette clé est une clé tweakée : elle encode à la fois « dépense par simple signature » et, cachée dedans, une arborescence de scripts alternatifs. Si tu dépenses par la voie simple, personne n’apprend jamais que l’autre voie existait. C’est le sujet de Scripts avancés et Taproot (BTC-A-09, à venir). Adresses bc1p….

Le témoin, et la faille qu’il a refermée

Reste la partie de la transaction qu’on a laissée de côté : les 109 octets de marker, flag et témoin. Pour comprendre pourquoi ils sont là, il faut revenir sur un défaut qui a hanté Bitcoin pendant huit ans.

Avant 2017, la signature était rangée dans le scriptSig, à l’intérieur du corps de la transaction — et donc hashée dans le txid. Or une signature ECDSA admet plusieurs encodages valides pour la même signature mathématique : on peut changer quelques octets de sa représentation sans la rendre invalide, et sans posséder la clé privée. N’importe quel nœud sur le chemin pouvait donc prendre une transaction en vol, en modifier l’encodage, et la rediffuser : même effet, même montants, même destinataire… mais un identifiant différent. C’est la malléabilité des transactions.

Le dégât n’est pas le vol — la malléabilité ne permet pas de détourner un satoshi. Le dégât, c’est que le txid devenait un point d’appui non fiable. Or tout protocole construit au-dessus de Bitcoin a besoin de signer à l’avance des transactions qui en référencent d’autres pas encore confirmées. Un canal Lightning, par exemple, repose sur une transaction de remboursement signée avant même que la transaction de financement ne soit dans un bloc. Si le txid de cette dernière peut changer sous vos pieds, le remboursement pointe dans le vide et l’argent est bloqué. Lightning était impossible à construire tant que la malléabilité existait.

txid et wtxid : ce que chaque identifiant hasheune seule transaction, deux empreintes différentestxid = SHA256d( 113 octets )la signature n'entre pas dans le calculversion4 omarker + flag2 oentrées42 osorties63 otémoin107 onLockTime4 owtxid = SHA256d( les 222 octets )témoin comprisCe que le txid identifiela transaction telle qu'elle a étévoulue : d'où viennent les fonds,où ils vont, sous quelles conditionsCe que le wtxid ajoutela preuve qu'on avait le droitde les déplacer — signatureet clé publique comprisesavant SegWit (2017), le témoin faisait partie du txid : ré-encoder une signature changeaitl'identifiant sans changer la transaction. C'était la malléabilité.
Deux identifiants pour une même transaction : le txid ne hashe que les 113 octets qui décrivent l’intention, le wtxid couvre les 222 octets, preuves comprises. La signature ne peut plus modifier le txid.

SegWit résout le problème par déplacement plutôt que par correction : on sort les signatures du calcul du txid. Le témoin est sérialisé après les sorties, et le txid se calcule sur la transaction comme si le témoin n’existait pas. Modifier une signature ne change donc plus rien à l’identifiant. Pour identifier la transaction complète, témoin compris, SegWit introduit un second identifiant, le wtxid — celui-là sert au relais réseau et à un arbre de Merkle dédié dans le bloc.

Sur notre exemple, les deux valeurs sont bien distinctes :

txid    b0d246e7e26433a81ddac344a0ba2240a0a7d06264de169c960d8d7d5353c72f
wtxid   c7377a55d130023b9a8448d0662f8fe9347babcaa3dde307d758bd8e9985fa11

Un mot sur une légende tenace. En février 2014, Mt. Gox a suspendu ses retraits en invoquant publiquement la malléabilité. L’analyse académique publiée dans la foulée par Decker et Wattenhofer a mesuré, sur plus d’un an de trafic réseau, 1 811 bitcoins concernés par des attaques de malléabilité avant l’arrêt des retraits — très loin des centaines de milliers manquants — et une explosion du phénomène après l’annonce, une fois la piste rendue publique.3 La malléabilité était un vrai problème de protocole ; elle n’a pas vidé Mt. Gox.

Ce qu’il faut retenir

Une transaction, en une imagedépenser, c'est ouvrir un verrou existant et en poser de nouveauxUN VERROU EXISTANTsortie d'une transaction passée2 754 952 satverrou : OP_0 <66f3af…46cf>bc1qvme67p5…fy768LA CLÉentrée + témointxid 65c57673… · vout 1signature 71 oclé publique 33 oDE NOUVEAUX VERROUSles sorties créées142 848 sat → bc1q7du4eec…2 611 314 sat → bc1qs9tzegk…chacune devient un UTXOdésigneproduitfrais = 2 754 952 − (142 848 + 2 611 314) = 790 satce qui n'est attribué à aucune sortie revient au mineur141 vB de poids facturé · 5,6 sat/vB · le txid ne couvre que la colonne de gauche et celle de droite, jamais la clé
La transaction complète en une lecture : un verrou existant, une clé qui l’ouvre, deux nouveaux verrous, et des frais qui sont simplement ce qu’on n’a pas réattribué.
  • Bitcoin ne stocke pas de soldes, mais des UTXO : des sommes indivisibles verrouillées par un script, dépensables uniquement en entier.
  • Une transaction est une structure binaire compacte : version, entrées, sorties, témoin, locktime. Tous les entiers sont en little-endian, tous les montants en satoshis.
  • Le verrou est un programme écrit en Script, volontairement non Turing-complet : validation à coût borné, pas de boucle, pas d’accès au monde extérieur.
  • Quatre familles d’outputs coexistent — P2PKH, P2SH, P2WPKH, P2TR — de 2009 à 2021, avec des coûts très différents, et Taproot n’est pas systématiquement le moins cher.
  • SegWit a sorti les signatures du txid, réglant la malléabilité et rendant Lightning constructible, tout en introduisant un système de poids qui remise les données du témoin d’un facteur 4.
  • Les frais ne sont écrits nulle part : ils sont la différence entre ce qui entre et ce qui sort.

Et après

On sait maintenant à quoi ressemble une transaction, et comment un nœud décide qu’elle est valide. Reste la question du quand : comment ces 222 octets se retrouvent dans un bloc, qui décide de les y mettre, et pourquoi il faut brûler de l’électricité pour ça.

C’est l’objet de BTC-A-03 — Le minage Bitcoin, en détail (à venir) : le header de 80 octets, le nonce, la difficulté et son ajustement tous les 2 016 blocs, et l’économie très concrète des fermes de calcul.

Cet article n’est pas un conseil en investissement.


  1. Transaction b0d246e7e26433a81ddac344a0ba2240a0a7d06264de169c960d8d7d5353c72f, confirmée dans le bloc 963 721 le 23 août 2026. Données brutes récupérées via l’API publique de mempool.space ; le txid, le wtxid, le poids et la taille virtuelle cités dans cet article ont été recalculés indépendamment à partir des 222 octets. ↩︎

  2. Les notes de version de Bitcoin Core 28.0 (octobre 2024) indiquent le passage de l’option -mempoolfullrbf de 0 à 1 par défaut, rendant le remplacement possible même sans signalement BIP125. ↩︎

  3. Christian Decker et Roger Wattenhofer, Bitcoin Transaction Malleability and MtGox , ESORICS 2014. Les auteurs recensent 1 811,58 BTC concernés par des attaques de malléabilité avant l’arrêt des retraits de Mt. Gox (février 2014), sur un total de 302 700 BTC impliqués sur toute la période observée — l’écrasante majorité étant donc postérieure à l’annonce. ↩︎

Série · Dans les rouages de Bitcoin 100% · 2/2
auto à 80% de scroll
N
nicolas
// rédacteur · solo

J'écris ce blog en français, à raison d'environ un article par semaine. L'objectif n'est pas de vous faire trader, mais de vous donner les outils pour décider seul.

newsletter / weekly

Un article comme celui-ci, chaque dimanche soir. 4 minutes. Pas de bruit.

double opt-in aucune pub /unsub 1 clic
commentaires[] remark42 · auto-hébergé · sans tracker