Bitcoin Transactions, Script & Signatures

A byte-to-state account of Bitcoin payments: UTXOs and outpoints, transaction serialization, locking and unlocking conditions, signature digests, SegWit, Taproot, identifiers, fees, and the boundary between consensus and relay policy.

A Bitcoin transaction does not debit one address and credit another. It consumes discrete unspent transaction outputs (UTXOs) and creates replacement outputs. Each consumed output already contains the conditions for spending it; the new transaction supplies the witness that satisfies those conditions and defines the next set of spendable objects.

The state object is an outpoint #

An ordinary output is a pair:

TxOut=(value in satoshis,  scriptPubKey).\text{TxOut} = (\text{value in satoshis},\; \text{scriptPubKey}).

Once its transaction is identified, the output is named by an outpoint (txid, output_index). A non-coinbase input contains that outpoint, a sequence number, and unlocking data. Validation looks up the referenced UTXO, executes the applicable spending rules, and—if the transaction is accepted—removes the old outpoint and inserts every new output under its new outpoint.

The arithmetic is conservation, not bookkeeping by address:

fee=∑input UTXO values−∑new output values≥0.\text{fee} = \sum \text{input UTXO values} - \sum \text{new output values} \ge 0.

In the diagram, the transaction consumes 120,000 sat, creates 118,000 sat, and leaves 2,000 sat as the fee. Inputs are indivisible references: if a wallet needs only part of a UTXO, it spends the whole output and creates an explicit change output. “Wallet balance” is therefore a local query over UTXOs the wallet believes it can spend.

What is serialized #

A base transaction serializes these top-level fields in order:

Field Role
version selects transaction-level semantics
input count + inputs names consumed outpoints; carries sequence and legacy unlocking script
output count + outputs assigns satoshi values to new locking scripts
lock_time earliest height or time at which the transaction may become final, subject to sequence rules

Each count and variable-length byte vector uses Bitcoin's CompactSize encoding. Fixed-width integers are generally little-endian on the wire. Transaction identifiers are conventionally displayed with their internal digest bytes reversed, a presentation rule that routinely confuses byte-level debugging.

SegWit adds a marker and flag after version, then serializes a witness field for every input before lock_time:

text
base:    version | inputs | outputs | lock_time
witness: version | marker | flag | inputs | outputs | witnesses | lock_time

The witness is not an extra Script program. It is a vector of stack items consumed by the rules of the referenced witness program. A P2WPKH witness, for example, supplies a signature and public key; the output's 20-byte program commits to the public-key hash.

Locking and unlocking are one validation program #

scriptPubKey is created with the output and fixes its spending condition. The later input supplies scriptSig, witness elements, or both, depending on output type. The names are historical: scriptPubKey can express more than a bare public key, and modern witnesses often carry the satisfying data outside scriptSig.

For legacy P2PKH, the output condition is conceptually:

text
OP_DUP OP_HASH160 <20-byte public-key hash> OP_EQUALVERIFY OP_CHECKSIG

The spending input pushes <signature> <public-key>. Evaluation checks that the supplied key hashes to the committed value and that the signature authorizes the rule-defined transaction digest. P2SH moves the committed condition behind a script hash. P2WSH does the same using a 32-byte SHA-256 witness program. Taproot instead commits to a 32-byte output key and permits either a key-path signature or revelation of one committed script leaf plus its Merkle path.

Bitcoin Script is deliberately constrained: it is stack-based and has no general loop construct. Its purpose is to decide whether a particular UTXO may be spent under deterministic, consensus-compatible limits—not to host an open-ended application runtime.

The signature message is not “the transaction hash” #

A signature authorizes a specifically constructed sighash message. The construction depends on the output type, script version, input being signed, and sighash flags. SIGHASH_ALL normally commits to all outputs; NONE, SINGLE, and ANYONECANPAY deliberately weaken which parts are committed. That flexibility is protocol functionality, but it means “the signature covers the transaction” is too vague to audit.

Spend generation Signature scheme Message construction Important properties
legacy Script ECDSA/secp256k1 legacy double-SHA-256 sighash serialization copies the previous locking script into the input being signed; historical edge cases and quadratic hashing behavior
SegWit v0 ECDSA/secp256k1 BIP143 double-SHA-256 digest reusable component hashes; commits to the spent input amount, which helps offline signers detect a false fee
Taproot / Tapscript BIP340 Schnorr/secp256k1 BIP341/342 tagged SHA-256 digest commits to amounts and scriptPubKeys; fixed-size signatures; key-path and script-path context are explicit

Bitcoin does not use SHA-1 in any of these transaction-signing paths. Legacy and SegWit-v0 authorization use double SHA-256 over different preimages. Taproot uses tagged SHA-256 constructions and BIP340 Schnorr signatures.

ECDSA and Schnorr coexist #

Bitcoin's historical script forms verify ECDSA on the secp256k1 curve. ECDSA requires a fresh secret nonce for every signature; reuse or bias can expose the private key. Its mathematical signatures are also malleable: (r,s) and (r,n-s) both verify unless the protocol enforces a canonical choice. Bitcoin added strict DER and low-s rules along the way, while deterministic nonce generation makes signing less dependent on runtime randomness.

Taproot introduced BIP340 Schnorr signatures over the same curve. A signature is a fixed 64-byte (R_x, s) encoding and a public key is a 32-byte x-only point encoding. The verification relation is linear, enabling sound multiparty constructions, and the specified scheme is strongly non-malleable under its assumptions. BIP342 makes Schnorr the signature primitive for Tapscript and replaces the old CHECKMULTISIG pattern with CHECKSIGADD-style constructions.

The schemes are not interchangeable merely because they use the same curve. The byte encoding, challenge calculation, nonce derivation, verification equation, and script rules all differ.

txid, wtxid, and malleability #

For a SegWit transaction:

  • txid is double SHA-256 of the traditional serialization without witness data;
  • wtxid is double SHA-256 of the serialization including marker, flag, and witness data.

Moving signatures out of the txid commitment prevents an uninvolved third party from changing a fully signed SegWit transaction's witness and thereby changing the outpoints created by that transaction. The witness is still committed into the block through a witness Merkle root anchored in the coinbase transaction. wtxid identifies the exact witness-bearing form for relay and validation.

This does not mean every semantic form of transaction malleability vanished. A signer can intentionally authorize alternatives through sighash flags or fee-replacement behavior, and legacy inputs retain historical properties. The precise claim is that SegWit separates transaction effects from authorization witness and fixes involuntary txid malleability for fully signed SegWit spends under its rules.

Validity, standardness, and mining are different gates #

A node does not accept a transaction merely because its signatures verify. Consensus checks include existing unspent inputs, value ranges, no duplicate spends, lock conditions, script success, and coin-creation rules. For an unconfirmed transaction, Bitcoin Core also applies local relay/mempool policy: standardness, minimum fee rate, ancestor/descendant limits, replacement rules, and resource controls.

Policy is stricter and configurable; it is not applied to transactions already inside a block. Consequently:

  • consensus-valid but non-standard transactions may be mined even if ordinary nodes refuse to relay them;
  • a transaction accepted by one mempool may be rejected or evicted by another;
  • mempool presence is not a confirmation;
  • miners choose transactions and ordering, but every full node independently validates the resulting block.

Fees are compared by rate, commonly satoshis per virtual byte. Transaction weight is base_size × 3 + total_size, and virtual size is weight divided by four, rounded up. Witness data is therefore discounted but never free.

A safe construction workflow #

  1. Select UTXOs and record each full previous output: outpoint, value, and scriptPubKey.
  2. Construct recipient and change outputs in satoshis; compute the intended fee and fee rate explicitly.
  3. Choose the output type and sighash mode deliberately.
  4. Present the complete semantic transaction to each signer; use PSBT when parties or devices need to exchange unsigned transaction data plus metadata.
  5. Finalize scriptSig and witness fields, then independently decode the final bytes.
  6. Recompute txid, wtxid, fee, weight, and every input's script result before broadcast.
  7. Test low-level experiments on regtest or a controlled test network. Never reuse demonstration keys or mainnet bytes from a tutorial.

Source claims audit #

The 2014 Shirriff article is preserved for its unusually concrete legacy transaction walkthrough, not as a current protocol specification.

Source claim or assumption Status Current account
inputs consume whole previous outputs; change is a new output current retained as the core UTXO mechanism
P2PKH scriptSig supplies a DER ECDSA signature and public key current for legacy P2PKH bounded to that output type; SegWit and Taproot paths added
every address is a Base58Check public-key hash outdated generalization P2SH, Bech32 witness programs, and Bech32m Taproot outputs use different commitments
transaction signing is legacy ECDSA over a modified serialization historically current legacy retained; BIP143 and BIP341/342 message construction added
re-signing changes the transaction identifier historically relevant but incomplete txid/wtxid separation and remaining malleability boundaries added
a fixed BTC amount is a typical fee outdated replaced by fee rate, weight, mempool policy, and demand
example Python can demonstrate key creation and signing unsafe for real funds the article itself warns about its RNG; current workflow requires audited libraries and signer review

Sources

  1. articleBitcoins the Hard Way: Using the Raw Bitcoin Protocolnotes
  2. docsBitcoin Transaction Reference
  3. docsBIP 141 — Segregated Witness
  4. docsBIP 143 — Transaction Signature Verification for Version 0 Witness Program
  5. docsBIP 340 — Schnorr Signatures for secp256k1
  6. docsBIP 341 — Taproot
  7. docsBIP 342 — Validation of Taproot Scripts
  8. docsBitcoin Core Transaction Relay Policy

Searches titles, aliases, headings, summaries, and full text.↑↓ move ↵ open esc close