Transaction Malleability
Here is a fact that surprises nearly every engineer the first time they hear it: before SegWit,
a transaction’s ID could change after you signed it — without invalidating the signature and without
changing where the money went. A third party (or even a miner) could take your valid transaction,
tweak it, and broadcast a version with a different txid that spent the same coins to the same
places.
This is transaction malleability, and understanding it teaches you something deep about what a signature does and doesn’t protect.
Recap: what a txid is
Section titled “Recap: what a txid is”A txid is the double-SHA-256 hash of the serialized transaction — the whole byte blob:
inputs, outputs, amounts, and (pre-SegWit) the unlocking scripts (scriptSig) that contain the
signatures.
txid = SHA256( SHA256( serialize(entire transaction including scriptSig) ) )That last clause is the whole problem. The signature is inside the bytes that get hashed, but a
signature cannot sign itself. So there’s a part of the transaction the signature does not commit
to — and if you can change that part without breaking the signature, you change the hash, and the
txid along with it.
Where the wiggle room came from
Section titled “Where the wiggle room came from”A Bitcoin signature commits to the inputs, outputs, and amounts — what is being spent and where
it goes. It does not commit to the exact byte-encoding of the scriptSig that carries it.
There were several ways to re-encode the same valid spend:
- ECDSA signature symmetry. For any valid ECDSA signature
(r, s), the value(r, -s mod n)is also a valid signature for the same message and key. Flipsto its complement and the signature still verifies — but the bytes differ, so the hash differs. (This is the “low-S” issue; BIP 62 / BIP 146 later required low-S to shut it down.) - DER encoding slack. Signatures were DER-encoded, and sloppy parsers accepted extra padding
bytes, non-minimal length encodings, etc. Add a harmless byte → different serialization → different
txid. - Script push-opcode equivalence. The same data can be pushed onto the stack with different
opcodes (
OP_PUSHDATA1vs a direct push), producing different bytes that execute identically.
In every case: same inputs, same outputs, same destination, same signature validity — different
txid.
Why this is dangerous: the broken assumption
Section titled “Why this is dangerous: the broken assumption”Malleability is not a theft vulnerability by itself — the attacker cannot redirect your coins; the outputs are signed. The danger is entirely about systems that assume a txid is stable.
Scenario 1 — the naïve withdrawal system (the “Mt. Gox excuse”)
Section titled “Scenario 1 — the naïve withdrawal system (the “Mt. Gox excuse”)”Imagine an exchange that processes withdrawals like this:
1. Build & sign a withdrawal tx, record its txid.2. Broadcast it.3. Poll the network: "has txid X confirmed?"4. If X never appears, assume the send failed → retry / re-credit the user.Now an attacker requests a withdrawal, then grabs the broadcast transaction, mutates it into X'
(same coins, same destination — they still receive their money), and rebroadcasts X' fast enough
that X' is the version miners confirm. The exchange is watching for X, never sees it, concludes
“the withdrawal failed,” and credits the user again or re-sends. The user has now been paid
twice, and the exchange’s books don’t balance.
When Mt. Gox collapsed in 2014, it publicly blamed transaction malleability for its missing coins. Researchers later showed malleability accounted for only a tiny fraction of the loss — Gox’s real problems were elsewhere — but the episode burned the concept into the industry’s memory and made the case for fixing it urgent.
Scenario 2 — the one that actually mattered: payment channels
Section titled “Scenario 2 — the one that actually mattered: payment channels”The deeper casualty was off-chain protocols. A Lightning-style payment channel works by building transactions that spend the output of a not-yet-broadcast transaction.
Funding tx ──► creates a 2-of-2 output (the channel) │ ▼Commitment / refund tx ──► spends that output, signed in advance, held offline as a safety netThe refund transaction’s input must reference the funding output by (txid, index). But if the
funding transaction is malleable, its txid can change the instant it’s broadcast — and now the
pre-signed refund transaction points at a (txid, index) that no longer exists. Your safety net
references a ghost. Your funds can be stuck.
This is why Lightning was not safely possible before malleability was fixed. You cannot build a tower of transactions on top of an unconfirmed one whose identity can shift under you.
The fix: SegWit (BIP 141)
Section titled “The fix: SegWit (BIP 141)”Segregated Witness (activated August 2017) fixed malleability at the root by segregating the
witness — moving the signatures and unlocking data out of the part of the transaction that the
txid commits to.
┌──────────────────────────────────────┐txid = │ version, inputs (txid:index, sequence)│ ← signatures NO LONGER in here │ outputs (amounts, scriptPubKey) │ │ locktime │ └──────────────────────────────────────┘ │wtxid = the above + ┌───────────────────────┐ │ witness (signatures) │ ← lives here, hashed separately └───────────────────────┘After SegWit:
- the
txidis computed over everything except the witness, so changing a signature’s encoding no longer changes thetxid; - a separate
wtxidcovers the witness too (used in the witness Merkle commitment).
Because the malleable bits (signatures) are excluded from the txid, the txid becomes stable
the moment you’ve decided the inputs and outputs — even before broadcast. That is what made
Lightning safe to build: you can now pre-sign a child transaction against a parent’s txid and trust
that the txid won’t move.
The thread
Section titled “The thread”How does this help untrusting strangers agree on one ledger? Malleability is a case where the ledger agreed on the right answer (the money moved correctly) while disagreeing on the name of the event. Fixing it didn’t change who-owns-what — it gave transactions a stable identity, which is the foundation every higher-layer protocol (channels, vaults, swaps) silently depends on.
Check your understanding
Section titled “Check your understanding”- Why can’t a signature commit to the entire transaction it’s part of?
- Give two distinct mechanisms by which a pre-SegWit transaction could be re-serialized to a new
txidwithout invalidating its signature. - Malleability can’t steal your coins. Explain precisely why a withdrawal-tracking system could still lose money because of it.
- Why does building a payment channel require a non-malleable funding transaction?
- In one sentence: what did SegWit physically move, and why did that fix the problem?