NEW BLOG POSTS
How Do Miners Validate Transactions?
Arthur Gordon
-
8 November 2024
This article follows on from “Building Blockchain Transactions in Python” which stepped through the process of constructing and broadcasting a transaction.
https://medium.com/@arthur.g.gordon/building-blockchain-transactions-with-python-f90aa06cd5f5
Note the code in this article is also provided as a Jupyter notebook to make it easier to follow, see here
https://github.com/nchain-innovation/build-tx-example/blob/main/validate_tx.ipynb
Transaction Signing
In the previous article we signed the transaction tx as follows:
>>>
input_to_sign =
0
>>>
signed_tx = my_wallet.sign_tx(input_to_sign, fund_tx, tx)
>>> signed_tx
PyTx { version:
1
, tx_ins: [PyTxIn { prev_tx:
"d261d1388e5a77119b6d6efb41d3610eb363750056895f4043c8cc7899f4cb17"
, prev_index:
0
, sequence:
4294967295
,
script_sig:
"0x3045022100aacb290ed3aeb43fc91a179d6a3ffef4c5efcca612c901c719e198c2ee685e2702200 505bd74db673c6d723a141bd8ac469327ea4bb7987110042f23f8c3d7f91e3d41 0x03dcf21dbdbaa744333af236c3382c85d6308e6d05599df5d3cb19e0f19a205d43"
}],
tx_outs: [PyTxOut { amount:
1000
, script_pubkey:
"OP_DUP OP_HASH160 0x4d7eb7ce5ce099dd218383f3f81d3c2f1e48113f OP_EQUALVERIFY OP_CHECKSIG"
}, PyTxOut { amount:
98800
, script_pubkey:
"OP_DUP OP_HASH160 0xd86625de492d8bd8bbc4930f2bef4328e37f1f53 OP_EQUALVERIFY OP_CHECKSIG"
}], locktime:
0
}
The signing method returned a complete new transaction, with an additional field script_sig, containing bitcoin script which has been completed by the signing operation. The script_sig contains two large numbers, these are the transaction signature and public key.
Bitcoin Script
The Bitcoin scripting language is based on Forth, a stack-oriented language. This means that values are pushed on the stack, and then operators pop the values off the stack. Once the operation is complete it pushes the result back onto the stack. For example the script:
> 1 2 OP_ADD
Will push the numbers 1 and 2 onto the stack and the OP_ADD will take the top two numbers from the stack, add them together and push the result (3) to the top of the stack. We can do this using tx-engine:
>>>
from
tx_engine
import
Script, Context
>>>
s = Script.parse_string(
"1 2 OP_ADD"
)
>>>
c = Context(script=s)
>>>
c.evaluate()
True
>>>
c.get_stack()
[
3
]
Note c.evaluate() returns True as the bitcoin script is valid.
The conditions for script to be valid are:
- nothing in the script triggers failure
- and the top stack item is non-zero (representing True), when the script exits
A full list of Bitcoin SV script operations can be found, here:
https://wiki.bitcoinsv.io/index.php/Opcodes_used_in_Bitcoin_Script
Note that Bitcoin SV (or BSV) supports additional operations over other Bitcoin Blockchains, this increases BSV’s utility for complex scripts, such as validating zero-knowledge proofs.
Transaction Validation
The script_sig script is also known as the unlocking script, and the script_pubkey as the locking script. This indicates how the two scripts operate together. The signing process places data in the script_sig (unlocking script) that will unlock the previous transaction’s script_pubkey (locking script).
In summary a script_pubkey is a challenge that the script_sig must provide data to unlock. Looking at our funding transaction, which was provided to the signing process, we have:>>>
fund_txid =
"86dc8d65e46bcab2ce44f9820b552b62707e9495e8caad6e3277689168526d2f"
>>>
fund_tx_as_hex_str = woc_interface.get_raw_transaction(fund_txid)
>>>
fund_tx = Tx.parse_hexstr(fund_tx_as_hex_str)
>>>
fund_tx
PyTx { version:
1
, tx_ins: [PyTxIn { prev_tx:
"d261d1388e5a77119b6d6efb41d3610eb363750056895f4043c8cc7899f4cb17"
, prev_index:
0
, sequence:
4294967295
,
script_sig:
"0x3045022100aacb290ed3aeb43fc91a179d6a3ffef4c5efcca612c901c719e198c2ee685e2702200 505bd74db673c6d723a141bd8ac469327ea4bb7987110042f23f8c3d7f91e3d41 0x03dcf21dbdbaa744333af236c3382c85d6308e6d05599df5d3cb19e0f19a205d43"
}],
tx_outs: [PyTxOut { amount:
1000
,
script_pubkey:
"OP_DUP OP_HASH160 0x4d7eb7ce5ce099dd218383f3f81d3c2f1e48113f OP_EQUALVERIFY OP_CHECKSIG"
},
PyTxOut { amount:
98800
,
script_pubkey:
"OP_DUP OP_HASH160 0xd86625de492d8bd8bbc4930f2bef4328e37f1f53 OP_EQUALVERIFY OP_CHECKSIG"
}], locktime:
0
}
So what do these script operations, in script_pubkey, do:
- OP_DUP ( n — — n n) Duplicates the top stack item
- OP_HASH160 (n — — hash) Hashes the top stack item with SHA-256 and RIPEMD-160 hash functions
- OP_EQUALVERIFY (x y — -) This checks that the top two stack items are equal, if not the script exits and marks the transaction as invalid
- OP_CHECKSIG (sig pubkey — -) The signature is checked against the hashed transaction and the provided signature and public key
By decoding the combined script_sig (unlocking script) and script_pubkey (locking script) we can see:
script_sig (unlocking script)
1
) Starting stack [] (where left
is
the top of the stack)
2
)
0x3045022100aacb290ed3aeb43fc91a179d6a3ffef4c5efcca612c901c719e198c2ee685e27022005 05bd74db673c6d723a141bd8ac469327ea4bb7987110042f23f8c3d7f91e3d41
[signature]
3
)
0x03dcf21dbdbaa744333af236c3382c85d6308e6d05599df5d3cb19e0f19a205d43
[public_key signature]
Script_pubkey (locking script)
4
) OP_DUP
[public_key public_key signature]
5
) OP_HASH160
[
hash
(public_key) public_key signature]
6
)
0xd86625de492d8bd8bbc4930f2bef4328e37f1f53
[
0x7b
…e1
hash
(public_key) public_key signature]
7
) OP_EQUALVERIFY (this could quit
with
False
at this point)
[public_key signature]
8
) OP_CHECKSIG
[]
True
Note that the script_sig (unlocking script) takes the hash of the public key, hence this is known as a Pay 2 Public Key Hash (P2PKH) transaction. This is one of the most commonly found transactions in Bitcoin.
Transaction Validation in Python
To validate this transaction the miner will first take the script_sig, from the transaction. In this case from our broadcast transaction:>>>
own_txid =
'72f201f87e3bb42bbd7f8eaed0f555a268a0eba5d0fb101160167c758ed863bb'
>>>
own_tx_as_hex_str = woc_interface.get_raw_transaction(own_txid)
>>>
own_tx = Tx.parse_hexstr(own_tx_as_hex_str)
>>>
own_tx
PyTx { version:
1
, tx_ins: [PyTxIn { prev_tx:
"86dc8d65e46bcab2ce44f9820b552b62707e9495e8caad6e3277689168526d2f"
, prev_index:
1
, sequence:
4294967295
,
script_sig:
"0x3045022100b09f980d91842499a82b696279d9f1590b7e31cef600ea0b60c41bd10e5b2f8702207 9a7a65e3090779a789de8892eac36aff5220e8c81d948c86dc61ebb7c08eb9441
0x03dcf21dbdbaa744333af236c3382c85d6308e6d05599df5d3cb19e0f19a205d43"
}],
tx_outs: [PyTxOut { amount:
1000
, script_pubkey:
"OP_DUP OP_HASH160 0x4d7eb7ce5ce099dd218383f3f81d3c2f1e48113f OP_EQUALVERIFY OP_CHECKSIG"
}, PyTxOut { amount:
97700
, script_pubkey:
"OP_DUP OP_HASH160 0xd86625de492d8bd8bbc4930f2bef4328e37f1f53 OP_EQUALVERIFY OP_CHECKSIG"
}], locktime:
0
}
The sig_script will place the signature and public key on the stack:
>>>
script_sig = Script.parse_string(
"0x3045022100b09f980d91842499a82b696279d9f1590b7e31cef600ea0b60c41bd10e5b2f8702207 9a7a65e3090779a789de8892eac36aff5220e8c81d948c86dc61ebb7c08eb9441
0x03dcf21dbdbaa744333af236c3382c85d6308e6d05599df5d3cb19e0f19a205d43"
)
The miner will then take the script_pubkey, from the funding transaction:
>>> script_pubkey = Script.parse_string(
"OP_DUP OP_HASH160 0xd86625de492d8bd8bbc4930f2bef4328e37f1f53 OP_EQUALVERIFY OP_CHECKSIG"
)
The miner evaluates sig_script and takes the resultant stack. The miner then passes this stack (with the signature and public key) to the script_pubkey which is then evaluated, if this returns true the transaction script is valid.
For the purposes of this exercise the operation will be simplified to combining the two scripts, as follows:
>>> combined_script = script_sig + script_pubkey
Then before we execute the scripts we will need to calculate the signature hash (sig_hash) of the transaction to provide to the OP_CHECKSIG operation. The miner will perform a similar operation to calculate the sig_hash (z) and provide it to the OP_CHECKSIG operation.
>>>
from
tx_engine
import
sig_hash, SIGHASH
>>>
z = sig_hash(own_tx,
0
, script_pubkey,
98800
, SIGHASH.ALL_FORKID)
Given the sig_hash function arguments:
* own_tx — Spending transaction
* 0 — Spending input index in the spending transaction
* script_pubkey — The lock_script of the output being spent
* 98800 — The satoshi amount in the output being spent
* SIGHASH.ALL_FORKID — Sighash flag
The ALL_FORKID flag is a combination of SIGHASH_ALL and SIGHASH_FORKID where:
- SIGHASH_ALL means sign all inputs and outputs
- SIGHASH_FORKID indicates that this is a BSV transaction
For further details of sighash flags see:
https://wiki.bitcoinsv.io/index.php/SIGHASH_flags
The transaction hash (z) is then provided to the execution Context, which then evaluates the combined script:
>>> x = Context(script=combined_script, z=z)
>>> x.evaluate()
True
Good work! you have just validated your first transaction.