The anatomy of a Bitcoin transaction

UTXOs, binary structure, the Script language, output families, witness and malleability: we take apart a real 222-byte Bitcoin transaction, field by field, until you can read it with the naked eye.

sommaire · 7 sections

In the previous article we looked at what Bitcoin signs with: an elliptic curve, a hash function, a key pair. That left the question that actually matters: what exactly is being signed?

The answer fits in an object smaller than a holiday photo. The transaction we’re taking apart here weighs 222 bytes. It was confirmed on 23 August 2026 in block 963,721, it is public, and by the end of this article you will be able to read every one of its bytes. Inside we’ll find the UTXO model, a miniature programming language, four different ways to lock the very same amount, and a flaw fixed in 2017 that made the Lightning Network buildable.

Bitcoin does not know your balance

First thing to unlearn: nowhere in Bitcoin is there a line saying “Nicolas: 1.45 BTC”. No account table, no balance field. What the blockchain holds is a set of unspent transaction outputs — UTXOs.

The analogy that works is the physical wallet. You don’t have “$47” in your pocket: you have a $20 note, a $10, two $5s, and some coins. When you pay $23, you don’t cut a note in half — you hand over the $20 and the $10, and you get $7 back. A UTXO is exactly that note: an indivisible amount, locked behind a condition, spendable only in full.

What your wallet displays as a “balance” is therefore a sum it computes itself, by scanning the chain for every UTXO it knows how to unlock. The protocol never performs that addition.

UTXO model · Bitcoinyou spend whole banknotes0.40 BTC0.15 BTC0.90 BTC3 UTXOs · displayed balance 1.45 BTCuntouchedtransactioninputs 0.55 → outputs 0.54990.50 → Bobnew UTXO0.0499 → methe change0.0001 left unassigned = miner feeboth input UTXOs are destroyed2 brand-new UTXOs replace themno balance is "edited" anywhereAccount model · Ethereumyou debit and credit a balancebeforeAlice1.45Bob2.00transfer 0.50afterAlice0.95Bob2.50two cells of a global ledgerare rewritten
Left, Bitcoin’s UTXO model: you consume whole “banknotes” and hand yourself the change. Right, Ethereum’s account model: two cells of a global ledger get rewritten.

The alternative exists and dominates elsewhere: the account model, as used by Ethereum. There, a transfer decrements one field and increments another. It is more intuitive for a human, more compact to store, and far more natural for a smart contract that has to maintain state.

So why did Satoshi go the other way? Three reasons still hold today:

  • Parallelisable validation. Two transactions touching different UTXOs are entirely independent: a node can check them at the same time, in any order. With accounts you have to serialise — otherwise two simultaneous debits on the same balance step on each other.
  • No global mutable state. Verifying a spend means answering a local question (“does this UTXO exist, and is its condition met?”), not querying a ledger that everyone is constantly rewriting.
  • Natural atomicity. A transaction consumes its inputs and creates its outputs in one shot. There is no intermediate state where money has left A without having reached B.

The price is real too. A wallet has to track each of its UTXOs individually, and fees depend on the number of notes you spend, not on the amount. Paying $10 with fifty 20-cent coins costs more than paying it with a note — hence the practice of consolidation, where you merge your small UTXOs during low-fee periods. And above all: every payment creates a change UTXO that comes back to you and leaves an exploitable trace. More on that in Privacy on Bitcoin (BTC-A-07, coming in this series).

222 bytes, read one by one

Enough theory. Here is the transaction as it travels across the network — 444 hexadecimal characters, i.e. 222 bytes:

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

This is not a teaching example cooked up for the occasion: it is transaction b0d246e7…5353c72f, mined in block 963,721.1 It spends one UTXO and creates two. Here’s how to slice it.

A real Bitcoin transaction, byte by bytetxid b0d246e7 ... 5353c72f · block 963,721 · 222 bytes on the wireoffsetsizefieldbytes (excerpt)04 Bversion02000000= 241 BSegWit marker0051 BSegWit flag0161 Binput count (varint)01= 1 input732 Bprevious txid (LE)cfae8669 ... 7376c565394 Boutput index01000000= vout 1431 BscriptSig length00= empty444 BnSequencefdffffffRBF + locktime481 Boutput count (varint)02= 2 outputs498 Boutput 1 value002e020000000000142,848 sat571 BscriptPubKey length16= 22 B5822 BscriptPubKey 10014 f3795ce7 ... 64648520P2WPKH808 Boutput 2 value72d82700000000002,611,314 sat881 BscriptPubKey length16= 22 B8922 BscriptPubKey 20014 81562ca2 ... 8ee11cd2P2WPKH1111 Bwitness item count0211272 Bsignature (1 B + 71 B)47 30440220 ... 4c4f55f001DER + SIGHASH18434 Bpublic key (1 B + 33 B)21 03e6332b ... 6f7322b3compressed2184 BnLockTime6eb40e00= 963,694INPUTOUTPUTSWITNESSmarker + flag + witness = the 109 bytes the txid never sees (SegWit)
The same transaction, field by field: offset, size, content. The three coloured blocks isolate the input, the outputs and the witness — this is the structure every Bitcoin node parses on receipt.

A few fields deserve a comment.

02000000 — the version. Four bytes, little-endian (least significant byte first), so the value is 2, not 33,554,432. Internalise this once and for all: in a Bitcoin transaction every integer is little-endian, including transaction identifiers, which we nonetheless display back-to-front. It is the number-one mistake of anyone writing their first parser.

00 01 — the marker and the flag. Two bytes that did not exist before 2017. The 00 sits where the input count belongs: since a transaction with zero inputs is impossible, an old node reading 00 concludes “nothing to see here” and stops — which is precisely the sleight of hand that allowed the witness to be added without a hard-fork. A modern node reads the 01 behind it and knows it is dealing with a SegWit transaction.

01 then 02 — the counters. These are varint — variable-length integers: below 253 the number fits in a single byte; above that, a prefix (fd, fe, ff) announces 2, 4 or 8 bytes. A 3-input transaction and a 300-input transaction therefore don’t spend the same number of bytes saying how many they have. This is byte-level optimisation, and Bitcoin is full of it.

cfae8669…7376c565 — the reference to the spent UTXO. Thirty-two bytes pointing at the transaction the money came from, followed by four bytes of index (01000000 = output number 1 of that transaction). This is the only way to designate money in Bitcoin: by its origin. Not by an address, not by a balance — by a (transaction, index) pair.

00 — the scriptSig length. Zero. The historical field where the signature used to go is empty, because this is a SegWit transaction: the signature has moved further down, into the witness. We’re getting there.

fdffffff — the nSequence. A field with a turbulent history. Today its value 0xfffffffd does two things: it enables nLockTime (any value below 0xffffffff does) and it signals opt-in RBF (BIP125 ), which allows the transaction to be replaced by a better-paying version while unconfirmed. One nuance: since Bitcoin Core 28 (October 2024), replacement is relayed by default, signal or no signal — the field stayed, its reach changed.2

002e020000000000 — a value. Eight bytes, little-endian again, denominated in Satoshi: 142,848 sat, i.e. 0.00142848 BTC. There is no decimal number anywhere in Bitcoin. Ever. “BTC” is a display convention, nothing more.

6eb40e00 — the nLockTime. 963,694, a block height — already in the past by the time the transaction confirms, so it blocks nothing. That is not a mistake: Bitcoin Core sets the locktime to the current height by default, a technique called anti-fee-sniping. The idea: if a miner were tempted to reorganise the last block to grab its fat transactions, those transactions would not yet be valid in the block it is trying to replace.

And the total? 142,848 + 2,611,314 = 2,754,162 sat going out, against 2,754,952 sat coming in. The difference — 790 satoshis — is written nowhere. It doesn’t need to be: in Bitcoin, the fee is whatever is left over. Anything you fail to assign to an output goes to the miner who includes the transaction. A buggy wallet that forgets its change output therefore gives the remainder away, and that has happened.

Script: the language of locks

Every output carries a spending condition. That condition is not an address type or a flag: it is a small program, written in a language specific to Bitcoin, called Script.

Script is stack-based: it has no variables, only a stack. Each instruction — an opcode — pops its arguments and pushes its result. And crucially, it is not Turing-complete: no loops, no backward jumps, no recursion. That is not a shortcoming, it’s a design decision. A Bitcoin script always terminates, in a number of steps bounded by its own length, which makes the cost of validating it predictable before you even run it. Ethereum made the opposite choice — Turing-completeness — and had to invent gas to stop infinite loops from freezing the network.

Unlocking happens in two parts. The output you want to spend carries a scriptPubKey (the lock). The transaction spending it supplies a scriptSig or a witness (the key). You concatenate the two, run them, and if the stack ends on a true value, the spend is authorised.

Here is the textbook case, P2PKH — the original 2009 form:

scriptPubKey   OP_DUP OP_HASH160 <20-byte hash> OP_EQUALVERIFY OP_CHECKSIG
scriptSig      <signature> <public key>
Running a P2PKH script — the stack, step by stepscriptPubKey (the lock) · OP_DUP OP_HASH160 <pkh> OP_EQUALVERIFY OP_CHECKSIGscriptSig (the key) · <signature> <public key>1push from scriptSigsigpubkey2OP_DUPduplicates the topsigpubkeypubkey3OP_HASH160hashes the topsigpubkeyH160(pk)4push <pkh>the address fingerprintsigpubkeyH160(pk)target pkh5OP_EQUALVERIFYcompares and popssigpubkeyelse: reject6OP_CHECKSIGchecks the signatureTRUEthe stack ends on a single true value → the spend is authorisedone false at any step and the transaction is rejectedtop of stack at the top
A P2PKH script running, with the stack visible at every step. Six instructions, no loop, one boolean result — that is all Bitcoin asks before authorising a spend.

Six opcodes are enough to understand 90% of what’s out there:

OpcodeWhat it does
OP_DUPduplicates the item on top of the stack
OP_HASH160replaces the top with its RIPEMD-160(SHA-256(x))
OP_EQUALVERIFYcompares the top two items, pops them, fails if they differ
OP_CHECKSIGchecks a signature against a public key, pushes true/false
OP_RETURNmakes the output permanently unspendable (see below)
OP_IF / OP_ELSE / OP_ENDIFconditional branching — the language’s only control structure

Two notes on the limits. First, Script sees nothing of the outside world: not the wall-clock date, not a price, not the contents of another transaction. It can only reason about what is pushed onto its stack and a few block-level facts. Second, part of the language was disabled in 2010 by Satoshi himself — including OP_CAT, which concatenated two items — because of bugs and denial-of-service risks in their implementation. Those opcodes still exist in the code but make any script invoking them fail. Whether they should come back is one of today’s big debates, which we’ll reopen at the end of the series.

Four families of locks

The same amount can be locked in four different ways, stacked up by fifteen years of protocol evolution. Each remains valid: Bitcoin has never retired a family already in circulation.

Four ways to lock the very same amountscriptPubKey · address prefix · real size of a 1-input / 2-output transactionfamilyscriptPubKey (the lock placed on the output)addresssizeP2PKH2009 · originalOP_DUP OP_HASH160 <20 B> OP_EQUALVERIFY OP_CHECKSIGthe whole lock sits in the output1...226 vBP2SH2012 · BIP16OP_HASH160 <20 B> OP_EQUALthe output holds only the script fingerprint3...166 vBP2WPKH2017 · BIP141OP_0 <20 B>signature out of the txid, discounted weightbc1q...141 vBP2TR2021 · BIP341OP_1 <32 B>one tweaked public key, key path or script pathbc1p...154 vBsizes measured on real transactions from block 963,721 (23 Aug 2026)Taproot is not always lighter: its output costs 12 B more, its input 41 weight units less
The four output families, their scriptPubKey, their address prefix and their real cost. Sizes are measured on four real 1-input, 2-output transactions taken from the same block.

P2PKH (Pay to Public Key Hash, 2009) — the whole lock is written in the output: duplicate, hash, compare, check the signature. Simple, readable, and the most expensive of the lot: 226 vB for a one-input, two-output transaction. Addresses start with 1.

P2SH (Pay to Script Hash, BIP16 , activated April 2012) — the conceptual flip. The output no longer holds the script, only its fingerprint. The real script, called the redeem script, is revealed only at spending time, by whoever spends. The practical consequence is enormous: the payer no longer has to know anything. Sending to a 2-of-3 multisig becomes as simple as sending to a normal address — the complexity moves to the receiving side. Addresses start with 3.

P2WPKH (SegWit v0, BIP141 , activated August 2017) — the output shrinks to OP_0 <20-byte hash>, and the signature moves into the witness. The gain is not cosmetic: witness data is billed four times less than the rest (see the box). Addresses bc1q…, like the ones in our example transaction. 141 vB.

P2TR (Taproot, BIP341 , activated November 2021) — the output is OP_1 <32-byte public key>, and that key is a tweaked one: it encodes both “spend with a plain signature” and, hidden inside it, a tree of alternative scripts. If you spend the simple way, nobody ever learns the other path existed. That’s the subject of Advanced scripts and Taproot (BTC-A-09, coming). Addresses bc1p….

The witness, and the flaw it closed

That leaves the part of the transaction we set aside: the 109 bytes of marker, flag and witness. To see why they’re there, we have to revisit a defect that haunted Bitcoin for eight years.

Before 2017, the signature lived in the scriptSig, inside the body of the transaction — and therefore hashed into the txid. But an ECDSA signature admits several valid encodings of the same mathematical signature: you can change a few bytes of its representation without invalidating it, and without holding the private key. Any node along the path could therefore grab a transaction in flight, alter its encoding and rebroadcast it: same effect, same amounts, same recipient… but a different identifier. That is transaction malleability.

The damage isn’t theft — malleability never let anyone divert a single satoshi. The damage is that the txid became an unreliable anchor. And every protocol built on top of Bitcoin needs to pre-sign transactions that reference other, not-yet-confirmed ones. A Lightning channel, for instance, relies on a refund transaction signed before the funding transaction is even in a block. If the funding txid can shift under your feet, the refund points at nothing and the money is stuck. Lightning was impossible to build while malleability existed.

txid and wtxid: what each identifier hashesone single transaction, two different fingerprintstxid = SHA256d( 113 bytes )the signature is left out of the hashversion4 Bmarker + flag2 Binputs42 Boutputs63 Bwitness107 BnLockTime4 Bwtxid = SHA256d( all 222 bytes )witness includedWhat the txid identifiesthe transaction as it was meantto be: where the funds come from,where they go, under what conditionsWhat the wtxid addsthe proof you were allowedto move them — signatureand public key includedbefore SegWit (2017), the witness was part of the txid: re-encoding a signature changed theidentifier without changing the transaction. That was malleability.
Two identifiers for one transaction: the txid hashes only the 113 bytes describing intent, the wtxid covers all 222 bytes, proofs included. A signature can no longer change the txid.

SegWit fixes the problem by relocation rather than repair: the signatures are taken out of the txid computation. The witness is serialised after the outputs, and the txid is computed on the transaction as if the witness did not exist. Altering a signature therefore no longer changes the identifier. To identify the complete transaction, witness included, SegWit introduces a second identifier, the wtxid — used for network relay and for a dedicated Merkle tree inside the block.

On our example, the two values are indeed different:

txid    b0d246e7e26433a81ddac344a0ba2240a0a7d06264de169c960d8d7d5353c72f
wtxid   c7377a55d130023b9a8448d0662f8fe9347babcaa3dde307d758bd8e9985fa11

A word on a stubborn legend. In February 2014, Mt. Gox halted withdrawals and publicly blamed malleability. The academic analysis published shortly after by Decker and Wattenhofer measured, over more than a year of network traces, 1,811 bitcoins involved in malleability attacks before withdrawals stopped — nowhere near the hundreds of thousands missing — and an explosion of the phenomenon after the announcement, once the trick had been made public.3 Malleability was a genuine protocol problem; it did not empty Mt. Gox.

What to take away

One transaction, in one picturespending means opening an existing lock and setting new onesAN EXISTING LOCKoutput of a past transaction2,754,952 satlock: OP_0 <66f3af…46cf>bc1qvme67p5…fy768THE KEYinput + witnesstxid 65c57673… · vout 1signature 71 Bpublic key 33 BNEW LOCKSthe outputs created142,848 sat → bc1q7du4eec…2,611,314 sat → bc1qs9tzegk…each one becomes a UTXOpoints atproducesfee = 2,754,952 − (142,848 + 2,611,314) = 790 satwhatever is left unassigned goes to the miner141 vB of billed weight · 5.6 sat/vB · the txid covers only the left and right columns, never the key
The whole transaction in one read: an existing lock, a key that opens it, two new locks, and a fee that is simply what nobody reassigned.
  • Bitcoin stores no balances, only UTXOs: indivisible amounts locked by a script, spendable only in full.
  • A transaction is a compact binary structure: version, inputs, outputs, witness, locktime. Every integer is little-endian, every amount is in satoshis.
  • The lock is a program written in Script, deliberately not Turing-complete: bounded validation cost, no loops, no access to the outside world.
  • Four output families coexist — P2PKH, P2SH, P2WPKH, P2TR — from 2009 to 2021, with very different costs, and Taproot is not systematically the cheapest.
  • SegWit took signatures out of the txid, settling malleability and making Lightning buildable, while introducing a weight system that discounts witness data by a factor of 4.
  • Fees are written nowhere: they are the difference between what goes in and what comes out.

What’s next

We now know what a transaction looks like, and how a node decides it is valid. What’s left is the question of when: how those 222 bytes end up in a block, who decides to put them there, and why electricity has to be burned for it.

That’s the subject of BTC-A-03 — Bitcoin mining, in detail (coming): the 80-byte header, the nonce, difficulty and its adjustment every 2,016 blocks, and the very concrete economics of compute farms.

This article is not investment advice.


  1. Transaction b0d246e7e26433a81ddac344a0ba2240a0a7d06264de169c960d8d7d5353c72f, confirmed in block 963,721 on 23 August 2026. Raw data pulled from the public mempool.space API ; the txid, wtxid, weight and virtual size quoted in this article were recomputed independently from the 222 bytes. ↩︎

  2. The Bitcoin Core 28.0 release notes (October 2024) record the -mempoolfullrbf option default moving from 0 to 1, making replacement possible even without BIP125 signalling. ↩︎

  3. Christian Decker and Roger Wattenhofer, Bitcoin Transaction Malleability and MtGox , ESORICS 2014. The authors count 1,811.58 BTC involved in malleability attacks before Mt. Gox stopped withdrawals (February 2014), out of a total of 302,700 BTC involved across the whole observed period — the overwhelming majority therefore coming after the announcement. ↩︎

Series · Inside the gears of Bitcoin 100% · 2/2
auto at 80% scroll
N
nicolas
// solo writer

I write this blog in French (translated to English), roughly one article per week. The goal isn't to make you trade — it's to give you the tools to decide on your own.

newsletter / weekly

One article like this, every Sunday evening. 4 minutes. No noise.

double opt-in no ads /unsub 1 click
comments[] remark42 · self-host · no tracker