Bitcoin Transactions


Introduction

You might be wondering why me (a jack of all trades, master of none) decided to dive into Bitcoin. My primary motivation is to contribute more to open-source software. Qualifying for the Summer of Bitcoin 2026 Challenge Round (Developer Track) and taking a blockchain elective this semester gave me the perfect push to start exploring the Bitcoin ecosystem.

Bitcoin Transactions Overview

I won't cover the entire concept of transactions in exhaustive detail here. If you want a deep dive, I highly recommend reading Grokking Bitcoin by Kalle Rosenbaum. He brilliantly explains transactions over five chapters, starting with a simple "Cookie token" example and progressively iterating on it to reflect real-world mechanics.

In short, a transaction is a message broadcast to the network indicating that a sender wishes to transfer a specific amount of Bitcoin to a receiver. It is signed by the sender using their private key. The network then validates this transaction and, once confirmed, adds it to the blockchain.

A transaction consists of two main components:

1. Inputs

  • The transaction ID (txId) of the previous transaction whose output is being spent.
  • The index (or vout) of that specific output.
  • Proof of ownership (such as a digital signature and public key) authorizing the spend.

2. Outputs

  • The amount of Bitcoin being transferred.
  • The recipient's address (often represented as a Public Key Hash or PKH), which acts as a lock on the funds.

The table below shows four distinct transactions, separated by horizontal lines. Notice how they vary from simple 1-to-1 transfers to more complex combinations with multiple inputs and outputs.

Table demonstrating four distinct Bitcoin transactions, illustrating various combinations of inputs and outputs

A new transaction is formed by taking the outputs of previous transactions, using them as inputs, and creating new outputs to assign the funds.

Diagram illustrating how a new Bitcoin transaction is formed by spending previous outputs as inputs to create new outputs

Let's break down what is happening in the second diagram:

  • Hashing (dSHA256): A transaction ID (txid) is generated by hashing the serialized transaction itself with double SHA-256 (legacy serialization). In SegWit transactions, the witness-inclusive hash is called the wtxid.
  • The Flow of Funds: The new transaction takes a 20 satoshi output from one prior transaction (belonging to PKH_23) and a 23 satoshi output from another (belonging to PKH_09). The total input amount is 43 satoshis.
  • Ownership and Signatures: As indicated by the brackets, the respective owners (PKH_23 and PKH_09) must provide their digital signatures and public keys to prove ownership and authorize spending their specific inputs.
  • Outputs and Miner Fees: The transaction creates two new outputs: 40 and 1 satoshis, totaling 41 satoshis. The difference between the total inputs and outputs (43 - 41 = 2 satoshis) serves as the implicit transaction fee, which is collected by the miner who includes this transaction in a block.

Anyone on the network can independently verify a transaction by checking these proofs of ownership for every output being spent.

Locking and Unlocking Scripts

In the simplified explanation above, we used terms like "address," "public key," and "digital signature." In reality, Bitcoin uses a flexible scripting language to handle these conditions:

  • Locking Script (ScriptPubKey): Placed on an output, this acts as a mathematical puzzle or a lock that specifies the conditions that must be met to spend the funds. The recipient's address is usually just a convenient representation of this script.
  • Unlocking Script (ScriptSig or Witness): Provided in an input, this contains the solution to the puzzle (like a digital signature and public key) to prove you have the right to spend the output.

Common Script Types

As mentioned above, the locking script (ScriptPubKey) defines how an output can be spent. Bitcoin has evolved to support several standard script types, each with its own specific use cases:

  • P2PK (Pay-to-Public-Key): The original, simplest script type where outputs are locked directly to a raw public key.
  • P2PKH (Pay-to-Public-Key-Hash): Introduced to improve security and usability by hiding the public key behind a hash until the output is spent. This is the classic Bitcoin address format.
  • P2SH (Pay-to-Script-Hash): Allows outputs to be locked to the hash of a more complex script (like a multisig setup). The sender only needs to send to a standard-looking address, and the receiver reveals the complex script when spending (BIP 16).
  • SegWit (Segregated Witness): A major upgrade that separated unlocking data (signatures) from the transaction data, fixing transaction malleability and increasing block capacity (BIP 141).
    • P2WPKH (Pay-to-Witness-Public-Key-Hash): The SegWit equivalent of P2PKH.
    • P2WSH (Pay-to-Witness-Script-Hash): The SegWit equivalent of P2SH, used for complex scripts.
  • P2TR (Pay-to-Taproot): The latest upgrade introducing Schnorr signatures and Taproot (BIP 340, BIP 341, BIP 342). It improves privacy and efficiency by making complex smart contracts look identical to standard single-signature transactions.
  • OP_RETURN: A provably unspendable output used to embed arbitrary data (up to 80 bytes) directly into the blockchain, often used for timestamping or secondary protocols.

Diving Deeper: Concrete Examples

Consider the following transaction:

{
  "network": "mainnet",
  "raw_tx": "0200000002aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000ffffffffbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0100000000feffffff02b80b0000000000001976a9140f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f88aca00f000000000000160014101010101010101010101010101010101010101000000000",
  "prevouts": [
    {
      "txid": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "vout": 0,
      "value_sats": 5000,
      "script_pubkey_hex": "76a914111111111111111111111111111111111111111188ac"
    },
    {
      "txid": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "vout": 1,
      "value_sats": 4000,
      "script_pubkey_hex": "76a914121212121212121212121212121212121212121288ac"
    }
  ]
}

That's a lot of data! Let's break it down step by step.

Network Context: mainnet

The "network": "mainnet" field indicates that this transaction is intended for the primary Bitcoin network, where real Bitcoin with actual monetary value resides. When building or testing applications, developers often use alternative networks like testnet or signet to experiment without risking real funds, or regtest for local development.

Parsing the Raw Transaction (raw_tx)

Visual breakdown of a raw Bitcoin transaction showing its version, inputs, outputs, and locktime fields

Note: You might be wondering why some fields are in reverse order. This is because Bitcoin uses little-endian byte order for its transactions. Why?

Let's walk through it field by field based on the diagram:

1. Version (02000000)

The transaction version number. Because Bitcoin uses little-endian encoding, we read this from right to left as 00000002, meaning this is a Version 2 transaction. Version 2 enables features like relative timelocks.

2. Inputs

  • Input Count (02): Indicates this transaction relies on 2 inputs.
  • Input [0]:
    • txId (aaaa...aaaa): The 32-byte hash (64 hex characters) of the previous transaction being spent.
    • Output Index (00000000): The specific output from that previous transaction. Read as little-endian 00000000 (Index 0).
    • ScriptSig Length (00): Indicates the unlocking script is 0 bytes long. This reveals that this transaction is unsigned. It's currently just a template; the signatures proving ownership have not been added yet.
    • Sequence (ffffffff): The sequence number (4 bytes). A sequence value less than the maximum (ffffffff) makes nLockTime relevant (a rule specifying an exact time or block height before which the transaction cannot be confirmed). Under BIP 125, opt-in Replace-By-Fee (RBF) is typically signaled when at least one input has nSequence < fffffffe. Because this specific input uses the max value (ffffffff), nLockTime is not enabled by this input and it does not signal opt-in RBF.
  • Input [1]:
    • txId (bbbb...bbbb): The previous transaction ID.
    • Output Index (01000000): Read in little-endian as 00000001 (Index 1).
    • ScriptSig Length (00): Also unsigned (0 bytes).
    • Sequence (feffffff): Read in little-endian as fffffffe. Since this value is less than the max (ffffffff), it makes nLockTime relevant for this input. It does not by itself signal BIP 125 opt-in RBF (that requires < fffffffe).

3. Outputs

  • Output Count (02): Indicates this transaction creates 2 new outputs.
  • Output [0]:
    • Amount (b80b000000000000): The number of satoshis being transferred. Read in little-endian as 0000000000000bb8, which is 3,000 satoshis in decimal.
    • ScriptPubKey Length (19): The length of the locking script in hexadecimal (19 hex = 25 bytes).
    • ScriptPubKey (76a9140f...0f88ac): This is the locking script. It effectively locks the 3,000 satoshis to the recipient's Bitcoin address.
  • Output [1]:
    • Amount (a00f000000000000): Read in little-endian as 0000000000000fa0, which is 4,000 satoshis in decimal.
    • ScriptPubKey Length (16): The length is 16 hex (22 bytes).
    • ScriptPubKey (00141010...1010): This represents the locking script.

4. Locktime (00000000)

A 4-byte value representing the transaction locktime. A value of 00000000 means the transaction is final and can be included in a block immediately.

The Role of prevouts

You may have noticed the prevouts array in our original JSON example. While the raw_tx field contains the actual transaction data broadcast to the network, the prevouts array provides crucial context about the inputs being spent.

Why do we need this extra information outside of the raw_tx?

  • Finding the Value: The inputs in raw_tx only provide a reference (txId and index) to the funds being spent. They do not include the actual Bitcoin amount (satoshis). To know how much is being spent, we must look up the original output on the blockchain. The prevouts array conveniently provides this value_sats for us.
  • Verifying Ownership: To verify a transaction, nodes must solve the mathematical puzzle placed on the original funds. The prevouts array includes the script_pubkey_hex (locking script) of the previous output, allowing us to validate the unlocking components provided in the new transaction's inputs.

Essentially, inputs point to where the funds were located, and prevouts describe the actual funds (with their values and locks) being retrieved. Block explorers and developer tools often bundle prevouts with the raw transaction so you have all the data needed to analyze the transaction without querying the blockchain yourself.

Calculating the Network Fee

As we discussed earlier, the fee goes to the miner and is simply the difference between the total inputs and total outputs. By looking at the prevouts array provided alongside our raw transaction, we can easily calculate this:

  • Total Inputs: 5,000 + 4,000 = 9,000 satoshis
  • Total Outputs: 3,000 + 4,000 = 7,000 satoshis

Therefore, the implicit miner fee for this transaction is 9,000 - 7,000 = 2,000 satoshis.

Decoding ScriptPubKey and ScriptSig

1. P2PKH Locking Script

Let's decode the following P2PKH (Pay-to-Public-Key-Hash) script byte by byte, using the Bitcoin Wiki on Script reference:

76a9140f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f88ac

Hex Opcode / Meaning Description
76 OP_DUP Duplicates the top stack item (the provided public key).
a9 OP_HASH160 Hashes the top stack item using SHA-256 followed by RIPEMD-160.
14 Push Data Pushes the next 0x14 (20 in decimal) bytes onto the stack.
0f0f...0f0f <Pub Key Hash> The 20-byte payload representing the recipient's public key hash.
88 OP_EQUALVERIFY Checks if the computed hash exactly equals the provided public key hash. Fails execution if they don't match.
ac OP_CHECKSIG Verifies the digital signature against the public key and the transaction data.

So, in the future, if someone wants to spend these coins, they must provide the appropriate ScriptSig.

Note: The ScriptSig in the example transaction was empty because the transaction was unsigned. When the transaction is signed, the ScriptSig will be populated with the corresponding unlocking code.

An appropriate ScriptSig for the above transaction would look like this:

2. P2PKH Unlocking Script (ScriptSig)

We can decode the corresponding ScriptSig byte by byte:

47304402206b005fe43818e31006a8abdafd9eaf04231bca6e001859844f6f70dc0af7e20b022026af80fedfbc76e626e6decead85fcc13bb7fbbcfea39162fa9c62c3e4c498ae012103e3b2eeb7bb1ddb91e9914619d85fd7dbb34ba84f478eab11b5e5bfdd9dbcd64b6e

Hex Opcode / Meaning Description
47 Push Data Pushes the next 0x47 (71 in decimal) bytes onto the stack.
3044...01 <Signature> The DER-encoded ECDSA signature plus a 1-byte sighash flag (01 for SIGHASH_ALL), proving authorization.
21 Push Data Pushes the next 0x21 (33 in decimal) bytes onto the stack.
03e3...6e <Pub Key> The sender's compressed public key. Its SHA256+RIPEMD160 hash must match the <Pub Key Hash> in the locking script.

The process of unlocking the funds which involves appending the ScriptSig to the ScriptPubKey and executing the resulting sequence of opcodes step-by-step on the Bitcoin script stack is illustrated in the following diagram:

Step-by-step execution of Bitcoin script opcodes on the stack to verify a P2PKH transaction

Note that the script verifies the public key hash indirectly, making it a P2PKH script.

3. P2WPKH Locking Script

Using the Bitcoin Wiki on Script reference, we can decode this P2WPKH (Pay-to-Witness-Public-Key-Hash) script byte by byte:

00141010101010101010101010101010101010101010

Hex Opcode / Meaning Description
00 OP_0 Pushes an empty array to the stack, indicating SegWit version 0.
14 Push Data Pushes the next 0x14 (20 in decimal) bytes onto the stack.
1010...1010 <Witness Program> The 20-byte payload representing the recipient's public key hash.

When spending a SegWit output like this, the unlocking signature and public key are not placed in the ScriptSig field. Instead, they are provided in a separate witness data section within the transaction.

Consider another transaction:

{
  "network": "mainnet",
  "raw_tx": "02000000000102a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a10000000000feffffffb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b20200000000fdffffff0210270000000000001600141313131313131313131313131313131313131313e02e00000000000022512014141414141414141414141414141414141414141414141414141414141414140247eed60c73dec1a4307bb6ade00521a58737c9e184e5f42329a9d82f63456c886ade14624e2f582c43767fffc7d06a49772d53847b711e86cb86cb46d018d9363b01f417e074a55421688bc8d5d9d8ab2ef6065ad8285a8faf92ca89371c9eb7478ec091bb36914b90440000000000",
  "prevouts": [
    {
      "txid": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
      "vout": 0,
      "value_sats": 15000,
      "script_pubkey_hex": "00141515151515151515151515151515151515151515"
    },
    {
      "txid": "b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2",
      "vout": 2,
      "value_sats": 9000,
      "script_pubkey_hex": "76a914161616161616161616161616161616161616161688ac"
    }
  ]
}

Visual breakdown of a raw SegWit transaction highlighting the segregated witness data structure

Anatomy of a SegWit Transaction

This second transaction is a SegWit transaction. Because SegWit separates ("segregates") the unlocking data from the transaction input data, the structure of raw_tx introduces new fields that were not present in our first example (BIP 141):

  1. SegWit Marker & Flag (0001): Located immediately after the Version (02000000), these two bytes (00 and 01) act as a signal to Bitcoin nodes that this transaction contains witness data.
  2. Witness: Located right after the Outputs and just before the Locktime. Rather than stuffing the unlocking signatures inside each input's ScriptSig, SegWit moves them to this dedicated witness structure. The witness block corresponds 1-to-1 with the inputs:
    • Witness[0]: Corresponds to Input[0]. Since this input spends a SegWit output (P2WPKH), its witness contains the unlocking information. It starts with an Item Count (02), followed by the two items:
      • Signature: Preceded by its length (47 hex or 71 bytes).
      • Public Key: Preceded by its length (21 hex or 33 bytes).
    • Witness[1]: Corresponds to Input[1]. It has an Item Count (00), meaning it's completely empty. Because this input spends a legacy P2PKH output, it does not use the witness structure. (Its unlocking data would instead go into its ScriptSig, which currently also shows a length of 00 because this particular input is unsigned).

Why SegWit Is So Beneficial in Practice

SegWit is often introduced as a format change, but its real value is in how it improves Bitcoin's behavior for users, wallets, and second-layer protocols.

  1. Lower Effective Fees (for the same transaction intent)
    • Bitcoin blocks now use weight units instead of only raw bytes.
    • Non-witness data counts 4x, while witness data is discounted (1x).
    • Since signatures move to witness, many transactions become lighter in virtual size (vbytes), so users often pay lower fees for equivalent payments.
  2. Higher Throughput Without a Hard Fork
    • SegWit increases the amount of useful transaction data a block can carry by changing how block limits are measured.
    • This means more transactions can fit per block during busy periods, helping reduce mempool pressure.
    • Importantly, this was activated as a soft fork, preserving compatibility with older nodes.
  3. Fixes Third-Party Transaction Malleability
    • Before SegWit, signatures were inside the txid preimage, so certain changes to signature encoding could alter the txid before confirmation.
    • With SegWit, witness data is separated from the base transaction identifier (txid), so external parties can no longer mutate the txid in the same problematic way.
    • This made transaction chaining and dependency tracking much more reliable.

Conclusion

If you made it this far, congratulations, you've officially spent more time with raw hex than most people spend choosing a Netflix show. We started with me trying to understand “how hard can Bitcoin transactions be?” and ended up decoding scripts, tracing UTXOs, calculating fees, and learning why byte order exists.

Now go and spend your time here: mempool.space.

References

  1. Grokking Bitcoin by Kalle Rosenbaum. An excellent visual guide to understanding Bitcoin's technical concepts. Read online.
  2. BIP 16 — Pay to Script Hash. Defines P2SH outputs. Read BIP 16.
  3. BIP 125 — Opt-in Full Replace-by-Fee Signaling. Defines opt-in RBF signaling rules using nSequence. Read BIP 125.
  4. BIP 141 — Segregated Witness (Consensus Layer). Introduces SegWit and witness structure. Read BIP 141.
  5. BIP 340 — Schnorr Signatures for secp256k1. Signature scheme used with Taproot. Read BIP 340.
  6. BIP 341 — Taproot: SegWit version 1 spending rules. Defines Taproot outputs and spending paths. Read BIP 341.
  7. BIP 342 — Validation of Taproot Scripts (Tapscript). Defines script validation changes for Taproot spends. Read BIP 342.