A fundamental feature of the Bitcoin network is the ability to transfer value (bitcoin) between participants in the network. Throughout history, the human species has always exchanged value in one way or another but what the Bitcoin network offers us now is the ability to programmatically set what we refer to as spending conditions (a set of requirement(s) that must be met before we can transfer value).
The language for constructing these conditions is the Bitcoin scripting language called Script, and yes the language is called Script.
Bitcoin uses the unspent transaction output (UTXO) model which means all the bitcoin available for potential spending exists as UTXOs. Attached to each UTXO is a script known as a locking script (or scriptPubKey) that describes the condition for spending that UTXO. This is one of the ways Script is used in Bitcoin transactions.
Before we look at some of the other ways Script is used in Bitcoin, let's highlight the features of the language
Features of Script
Stack-based: The code runs in a linear format from left to right following the popular stack data structure which uses the LIFO (last in first out) ordering. The execution is similar to the Forth-like programming language used in some calculators.
Turing incomplete: Predictable execution time with no looping construct/"goto" functionality. This is to prevent any potential DDoS attack/infinite loop
Deliberately simple and limited in scope - Making it able to run in a wide range of hardware devices
Mostly used in transaction validation/verification
So how does Script work
Script operates in a reverse polish notation format, which can be illustrated with an example:
The operation 1 + 1
in reverse polish notation will be 1 1 +
To run this operation in a Script, it will involve
Pushing the 1 onto the execution stack
Pushing the second value 1 into the stack
And the next item is the + which is an operator that will accept two values from the top of the stack. The two values are popped off the stack and added together before pushing the resulting value onto the stack
Writing this action in Script will look like this
OP_1 OP_1 OP_ADD
In visual terms
Let's try another example
5 4 OP_ADD 9 OP_EQUAL
Note: 1 = true and 0 = false
This explains the basic operation and concept around Script, as earlier stated Script powers Bitcoin transactions i.e for a transaction to be valid or invalid, the final value of the script execution has to be valid or invalid respectively
Script is mostly made up of operators (op-codes) and conditions, Let's explore some of the use cases of the language in our regular Bitcoin usage.
The major usage is applied in the locking and unlocking script
Locking script - Spending condition(s) placed on an output
Unlocking script - Satisfies the condition(s) specified by the locking script
To validate a Bitcoin transaction the node has to copy the unlocking script and retrieve all the UTXOs referenced by the inputs. The inputs are validated separately by executing the locking and unlocking script in sequence. An input is valid only if the unlocking script satisfies the locking script.
Using a script we have seen before imagine if this is the locking script for an input5 OP_ADD 9 OP_EQUAL
Can you guess the unlocking script that will result in a valid transaction?
Yes you're right, the answer is OP_4
or 4
(OP can always be omitted)
Even though it is totally possible to have a trivial locking and unlocking script as above, let's dive into one of the most used scripts in the bitcoin network - The Pay To Public Key Hash Script (P2PKH).
Pay To Public Key Hash
In most Bitcoin transactions the outputs contain a locking script that locks the output to a public key hash, more commonly known as a bitcoin address. In this case, the locking script is of the form:
OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG
In the context of an actual transaction i.e if Emeka is sending bitcoin to Tunde
The above locking script can be satisfied with an unlocking script of the form:<Tunde Signature> <Tunde Public Key>
The result will be TRUE if the unlocking script has a valid signature from Tunde's private key that corresponds to the public key hash set in the locking script.
Combining the scripts and Evaluating we get a valid transaction.
<TundeSig> <TundePubK> + OP_DUP OP_HASH160 <Tunde Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG
Let's visualize the execution of the combined script
Control flow in Scripts
Just like most programming languages, we can perform flow conditions in Scripts too but of course with some differences.
Flow control is implemented using the IF, ELSE, ENDIF, and NOTIF opcodes, boolean operators such as BOOLAND, BOOLOR, and NOT can also be used.
Let's examine the major difference between flow control in a regular language vs Script using pseudocode
Flow control for most languages
if(condition):
code to run when condition is true
else:
code to run when condition is false
code to run in either case
Flow control in Script
condition
IF
code to run when condition is true
ELSE
code to run when condition is false
ENDIF
code to run in either case
Notice that the condition comes before the IF opcode
Flow controls in Script enable us to provide different execution paths for spending a transaction. Conditions can be nested as much as possible giving room for really complex constructs
IF
<Emeka's Pubkey> CHECKSIG
ELSE
<Tunde's Pubkey> CHECKSIG
ENDIF
Looking at the above, it shows a locking script where we have two signers, Emeka and Tunde, and either one is able to redeem the funds.
Also, notice the condition for the IF operator is missing, this is the case because the condition will be passed as part of the unlocking script.
We have been able to see some simplified explanations of Script, But with the presence of various opcodes and unlimited conditional nesting, a lot can and has been constructed using this limited but powerful language.
The lightning network which is a layer 2 Bitcoin solution makes use of really complex scripts using Script to achieve its use cases.
Here is a snippet just for the curious.
# To remote node with revocation key
OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
OP_IF
OP_CHECKSIG
OP_ELSE
<remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
OP_NOTIF
# To local node via HTLC-timeout transaction (timelocked).
OP_DROP 2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
OP_ELSE
# To remote node with preimage.
OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
OP_CHECKSIG
OP_ENDIF
1 OP_CHECKSEQUENCEVERIFY OP_DROP
OP_ENDIF
Conclusion
As we can see the Bitcoin scripting language is really what qualifies bitcoin as programmable money, the examples and Opcode highlighted above are just some of the ways Script can be useful, to see all the available opcodes check out https://en.bitcoin.it/wiki/Script#Opcodes.
With a combination of these opcodes and desired conditions, we can implement different spending options.
You can also find a playground here https://siminchen.github.io/bitcoinIDE/build/editor.html where you can run some Script and visualize its execution in a simulated stack visualizer.
And lastly, I will appreciate any questions or feedback
Remain Curious
References
https://learnmeabitcoin.com/technical/script
https://github.com/bitcoinbook/bitcoinbook