Bitcoin is sometimes thought of as programmable money. Due to its digital nature, it allows users a high degree of flexibility when it comes to setting conditions on how money can be spent. In this article, we will take a closer look at Script, the programming language interpreted by nodes on the Bitcoin network. The script is what governs the lock/unlock mechanism mentioned for the safe.
We talk about wallets and coins when we discuss Bitcoin. But we can also think of wallets as keys, coins as checks, and blockchains as rows after rows of locked safes. Every safe has a thin slot in it so anyone can deposit a check or look in to see how much the safe is worth. However, only the person holding the key can access the inside.
When the key holder wants to give money to someone else, they unlock their box. They create a new check that references the older (later destroyed) one and lock it in a box that the recipient can open. The new recipient repeats the process to spend that.
How does Bitcoin work?
Running with our analogy from above, you can say that there are two parts to each transaction – a key (to open your box) and a padlock. You use your key to open the box containing the check you want to deposit, then you add a new check to the new box with a different key. You need another key to spend from the new box.
Simple enough. You can also have a slight variation on the key types in the system. Maybe some safes require you to provide multiple keys and maybe others need you to prove that you know a secret. There is a wide range of conditions that people can set.
Our key is what we call scriptSig . The key is our scriptPubKey. If we look at those components in a little more detail, we will see that they are actually made up of data bits and code blocks. When combined, they create a small program.
When you make a transaction, you are broadcasting that combination to the network. Each node that receives it checks the program, which will tell it whether the transaction is valid or not. Otherwise, it will just be discarded and you will not be able to use the locked amount.
The checks (coins) you hold are called unused transaction output (UTXO). Money can be used by anyone who can provide a key that matches the lock. Specifically, the key is scriptSig and the key is scriptPubKey.
If UTXOs are in your wallet, they will likely have a condition saying that only someone who can prove ownership of this public key can unlock these funds. To unlock, you provide a scriptSig that includes a digital signature, using a private key that maps to the public key specified in scriptPubKey. It will be clearer shortly.
Understanding the Bitcoin Stack
Script is known as a stack-based language. All of this means that, when we read a set of instructions, we put them in what can be thought of as a vertical column. For example, the list A, B, C will result in a stack with A at the bottom and C at the top. When the instructions tell us to do something, we operate on one or more elements starting at the top of the stack.
We can distinguish between data (things like signatures, hash codes, and public keys) and instructions (or opcodes). The instructions erase the data and do something about it. Here is a very simple example of what the script might look like:
<xyz> <md5 hasher>
<d16fb36f0911f878998c136191af705e> <check if equal>
In red, we have the data and in blue we have the optical codes. We read from left to right, so we put the string <xyz> first on the stack. Next is the opcode <md5 hasher> . This doesn’t exist in Bitcoin, but assumes that it removes the top element of the stack ( <xyz> ) and hashes it using the MD5 algorithm. Then the output is added back to the stack. The output here is d16fb36f0911f878998c136191af705e.
It is coincidence! Our next element to add is <d16fb36f0911f878998c136191af705e> , so now our stack has two identical elements. Finally, <check if equals> pops two elements to the top and checks if they are equal. If it does, it will add <1> to the stack. Otherwise it will add <0> .
We have reached the end of our list of tutorials. Our script can fail in two ways – if the remaining element is zero, or if one of the operators causes it to fail when some condition is not met. We don’t have any such operator in this example and we end up with a non-zero element ( <1> ), so our script is valid. These rules also hold true for real Bitcoin transactions.
It’s just a generated program. Now let’s look at some facts.
Pay-to-Pubkey (P2PK) is super simple. It involves locking coins to a specific public key. If you wanted to receive funds this way, you would provide the sender with your public key, as opposed to a Bitcoin address.
The first transaction between Satoshi Nakamoto and Hal Finney in 2009 was a P2PK. This structure was heavily used in the early days of Bitcoin, but today, Pay-to-Pubkey-Hash (P2PKH) has largely replaced it.
The locking script for P2PK transactions follows the <public key> OP_CHECKSIG format. Simple enough. You can guess that OP_CHECKSIG checks the signature against the provided public key. As such, our scriptSig will be a simple <signature> . Remember that scriptSig is the key of the lock.
A signature is added to the stack, followed by a public key. OP_CHECKSIG turns on both and verifies the signature based on the public key. If they match, it adds a <1> to the stack. Otherwise, it will add a <0> .
For reasons that we will explain in the next section, P2PK is not really used anymore.
Pay-to-Pubkey-Hash (P2PKH) is currently the most popular type of transaction. Unless you try to download classic software, your wallet probably does these things by default.
The ScriptPubKey in P2PKH is as follows:
OP_DUP OP_HASH160 <public key hash> OP_EQUALVERIFY OP_CHECKSIG
Before we introduce scriptSig, let’s analyze what the new opcodes will do:
OP_DUP pops the first element and copies it. Next, it adds both back to the stack. Usually this is done so that we can perform an operation on the copy without affecting the original.
This will pop the first element and hash it twice. The first round will hash using the SHA-256 algorithm. The SHA-256 output is then hashed using the RIPEMD-160 algorithm. The output is added back to the stack.
OP_EQUALVERIFY combines two other operators OP_VERIFY. OP_EQUAL and – OP_EQUAL pops two elements and checks if they are the same. If it does, it will add 1 to the stack. If not, it adds 0. OP_VERIFY pops the top element and checks if it is true (i.e. non-zero). Otherwise, the transaction will fail. Combined, OP_EQUALVERIFY causes the transaction to fail if the top two elements do not match.
The scriptSig then looks like this:
<signature> <public key>
You need to provide signature and corresponding public key to unlock P2PKH output.
You can see what’s happening in the GIF above. It’s not too different from a P2PK script. We just add one more step to check if the public key matches the hash in the script.
However, there are a few things to keep in mind. In the P2PKH key script, the public key is not visible – we can only see its hash. If we go to a blockchain explorer and see the unspent P2PKH output, we cannot determine the public key. It is revealed only when the recipient decides to transfer the money.
This has a few benefits. The first is that hashing a public key is simply easier to transfer than a full public key. In 2009, Satoshi launched it for this very reason. The hashed public key is what we know as a Bitcoin address today.
A second benefit is that public key hashing can provide an additional layer of security against quantum computing. Because our public key is not known until we spend the money, it is even harder for others to compute the private key. They would have to reverse two rounds of hashing (RIPEMD-160 and SHA-256) to get it.
Pay-to-Script-Hash (P2SH) is a very interesting development for Bitcoin. It allows the sender to lock coins into the hash of a script – they don’t need to know what the script actually does. For example the following SHA-256 hash:
You don’t need to know the input of the hash to lock coins into it. However, the spender needs to provide the script that was used to hash it and needs to meet its conditions.
The hash above is generated from the following script:
<multiply by 2> <4> <check if they are equal>
If you want to spend the money associated with that scriptPubKey, you don’t just provide those commands. You also need a scriptSig that causes the completed script to evaluate to True. In this example, it’s an element that you <multiply by 2> to get <4> . Of course, it means our scriptSig is just <2> .
In real life, the scriptPubKey for P2SH output is:
OP_HASH160 <RedScript hash> OP_EQUAL
There are no new operators here. However, we have <RedScript hash> as a new element. As the name suggests, it’s a hash of the script that we need to provide for the exchange (called RedScript ). The ScriptSig will change depending on what’s in RedScript. In general, however, you’ll find that it’s some combination of signature and attached public keys, followed by RedScript (required):
<signature> <public key> <redeemScript>
Our assessment differs a bit from the stack implementation we’ve seen so far. It happens in two parts. The first is simply to check that you have provided the correct hash.
You’ll note that we’re not doing anything with pre-redScript elements. At this time, they are not used. We’ve reached the end of this little program and the top element is non-zero. That means it’s valid.
However, we are not done yet. Network nodes recognize this structure as P2SH, so they actually have the elements of scriptSig waiting in another stack. That’s where the signature and public key will be used.
So far, we’ve treated RedScript as an element. But for now, it will be interpreted as instructions, can be anything. Let’s take the example of a P2PKH key script, for which we have to provide <signature> and <public key> matching <public key hash> inside <redeemScript> .
Once your RedScript has been extended, you can see that we have an identical situation as a regular P2PKH transaction. From there, you can simply run it as you normally would.
We’ve demonstrated what’s called a P2SH (P2PKH) script here, but you’d be hard-pressed to find one of those in the wild. There’s nothing stopping you from creating one, but it gives you no additional benefit and will end up taking up more space in a block (and thus, higher cost).
P2SH is often useful for things like multisignature or transaction-compatible SegWit. Multisig transactions can be very large in size as they may require multiple locks. Before implementing Pay-to-Script-Hash, the sender will have to list all possible public keys in their key script.
But with P2SH, it doesn’t matter how complicated the spending conditions are. RedScript’s hash is always a fixed size. The cost is therefore passed on to the user(s) who wish to unlock the lock script.
SegWit compatibility is another case where P2SH comes in handy (we’ll go into detail on how the transaction structure differs in the next section). SegWit is a soft fork that led to a change to block/transaction formats. Because this is an opt-in upgrade, not all wallet software recognizes the changes.
It doesn’t matter if the client wraps the SegWit script hash in P2SH. As with all transactions of this type, they do not need to know how RedScript unlocks.
SegWit Transactions (P2WPKH and P2WSH)
For a more comprehensive introduction to SegWit, check out a Beginner’s Guide to Separation Witness.
To understand the transaction format in SegWit, you just need to know that we no longer just have scriptSig and scriptPubKey. Now, we also have a new field called witness. The data that we use to keep in scriptSig is passed to the witness, so scriptSig is empty.
If you’ve come across addresses that start with ‘bc1’, that’s what we call SegWit-native (as opposed to just being SegWit-compatible, which starts with ‘3’ because they’re P2SH addresses).
Pay-to-Witness-Pubkey-Hash (P2WPKH) is the SegWit version of P2PKH. Our witness looks like this:
<signature> <public key>
You will note that this is the same as scriptSig from P2PKH. Here, scriptSig is empty. Meanwhile, the scriptPubKey looks like this:
<OP_0> <public key hash>
Looks a bit strange, doesn’t it? Where are the opcodes to allow us to compare its signature, public key and hash?
We don’t show additional operators here, because the nodes receiving the transaction know what to do with it based on the length of the <public key hash> . They will calculate the length and understand that it should be run in the same style as a good P2PKH transaction.
Non-upgraded nodes don’t know how to interpret the transaction that way, but that doesn’t matter. According to the old rule, there are no witnesses, so they read an empty scriptSig and some data. They evaluate this and mark it as valid – as far as they are concerned, anyone can spend the output. This is why SegWit is considered a backward compatible soft fork.
Pay-to-Witness-Script Hash (P2WSH) is the new P2SH. If you’ve made it this far, you can figure out what it looks like, but we’ll run through anyway. Our witness is what we usually put in scriptSig. For example, in a P2WSH closing a P2PKH transaction, it might look something like this:
<signature 1> <public key>
Here is our scriptPubKey:
<OP_0> <script hash>
The same rules are held. SegWit nodes read the length of the script hash and determine that it is a P2WSH output, which is evaluated similarly to P2SH. Whereas the old nodes only see it as an output that anyone can spend.
Closing thoughts : Script type
In this article, we learned a bit about the building blocks of Bitcoin. Let’s summarize them quickly:
As you dig deeper into Bitcoin, you begin to understand why it has so much potential. From many different components, transactions can be created. By manipulating these building blocks, users have a lot of flexibility when it comes to setting the conditions for how and when the money can be spent.