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.

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

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 thewtxid. - 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 toPKH_09). The total input amount is 43 satoshis. - Ownership and Signatures: As indicated by the brackets, the respective owners (
PKH_23andPKH_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 (
ScriptSigor 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)

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-endian00000000(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) makesnLockTimerelevant (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 hasnSequence < fffffffe. Because this specific input uses the max value (ffffffff),nLockTimeis not enabled by this input and it does not signal opt-in RBF.
- txId (
- Input [1]:
- txId (
bbbb...bbbb): The previous transaction ID. - Output Index (
01000000): Read in little-endian as00000001(Index 1). - ScriptSig Length (
00): Also unsigned (0 bytes). - Sequence (
feffffff): Read in little-endian asfffffffe. Since this value is less than the max (ffffffff), it makesnLockTimerelevant for this input. It does not by itself signal BIP 125 opt-in RBF (that requires< fffffffe).
- txId (
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 as0000000000000bb8, which is 3,000 satoshis in decimal. - ScriptPubKey Length (
19): The length of the locking script in hexadecimal (19hex = 25 bytes). - ScriptPubKey (
76a9140f...0f88ac): This is the locking script. It effectively locks the 3,000 satoshis to the recipient's Bitcoin address.
- Amount (
- Output [1]:
- Amount (
a00f000000000000): Read in little-endian as0000000000000fa0, which is 4,000 satoshis in decimal. - ScriptPubKey Length (
16): The length is16hex (22 bytes). - ScriptPubKey (
00141010...1010): This represents the locking script.
- Amount (
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_txonly provide a reference (txIdand 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. Theprevoutsarray conveniently provides thisvalue_satsfor us. - Verifying Ownership: To verify a transaction, nodes must solve the mathematical puzzle placed on the original funds. The
prevoutsarray includes thescript_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
ScriptSigin the example transaction was empty because the transaction was unsigned. When the transaction is signed, theScriptSigwill 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:

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"
}
]
}

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):
- SegWit Marker & Flag (
0001): Located immediately after the Version (02000000), these two bytes (00and01) act as a signal to Bitcoin nodes that this transaction contains witness data. - 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 (
47hex or 71 bytes). - Public Key: Preceded by its length (
21hex or 33 bytes).
- Signature: Preceded by its length (
- 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 itsScriptSig, which currently also shows a length of00because this particular input is unsigned).
- 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 (
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.
- 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.
- 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.
- Fixes Third-Party Transaction Malleability
- Before SegWit, signatures were inside the
txidpreimage, so certain changes to signature encoding could alter thetxidbefore confirmation. - With SegWit, witness data is separated from the base transaction identifier (
txid), so external parties can no longer mutate thetxidin the same problematic way. - This made transaction chaining and dependency tracking much more reliable.
- Before SegWit, signatures were inside the
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
- Grokking Bitcoin by Kalle Rosenbaum. An excellent visual guide to understanding Bitcoin's technical concepts. Read online.
- BIP 16 — Pay to Script Hash. Defines P2SH outputs. Read BIP 16.
- BIP 125 — Opt-in Full Replace-by-Fee Signaling. Defines opt-in RBF signaling rules using
nSequence. Read BIP 125. - BIP 141 — Segregated Witness (Consensus Layer). Introduces SegWit and witness structure. Read BIP 141.
- BIP 340 — Schnorr Signatures for secp256k1. Signature scheme used with Taproot. Read BIP 340.
- BIP 341 — Taproot: SegWit version 1 spending rules. Defines Taproot outputs and spending paths. Read BIP 341.
- BIP 342 — Validation of Taproot Scripts (Tapscript). Defines script validation changes for Taproot spends. Read BIP 342.