Bitcoin is sometimes referred to as programmable money. Because of its digital nature, it allows users a great degree of flexibility when it comes to setting conditions for how funds can be spent.
We speak of wallets and coins when discussing Bitcoin. But we could also think of wallets as keys, coins as cheques, and the blockchain as row after row of locked safes. Each safe has a thin slot in it, such that anyone can deposit cheques or look in to see how much value the safe holds. However, only the key holder will be able to access the inside.
When a key holder wants to give money to someone else, they unlock their box. They make out a new cheque that references the older one (which is then destroyed) and lock it in a box that the recipient can open. To spend that, the new recipient repeats the process.
In this article, we’ll take a closer look at Script, the programming language interpreted by nodes on the Bitcoin network. Script is what governs the locking/unlocking mechanism mentioned for the safes.
Running with our analogy from above, you could say that there are two parts to every transaction – a key (to unlock your box) and a lock. You use your key to open the box that contains the cheque you want to send, and then you add a fresh one to a new box with a different lock. To spend from the new box, you need another key.
Simple enough. You can also get a bit of variation on the types of locks in the system. Maybe some safes require that you provide multiple keys, and maybe others need you to prove that you know a secret. There’s a bunch of conditions that people can set.
Our key is what we call a scriptSig. The lock is our scriptPubKey. If we look at those components in a bit more detail, we’ll find that they’re actually made up of bits of data and blocks of code. When combined, they create a tiny program.
When you make a transaction, you’re broadcasting that combination to the network. Each node that receives it will check the program, which tells it whether the transaction is valid. If not, it’ll just be discarded, and you won’t be able to spend the locked funds.
The cheques (coins) you hold are called unspent transaction outputs (UTXOs). The funds can be used by anyone that can provide the key that fits the lock. Specifically, the key is the scriptSig and the lock is the scriptPubKey.
If the UTXOs are in your wallet, they’ll probably have a condition that says only the person who can prove ownership of this public key can unlock these funds. To unlock it, you provide a scriptSig that includes a digital signature, using the private key that maps to the public key specified in the scriptPubKey. This will all become clearer shortly.
Script is what’s known as a stack-based language. All this means is that, when we read a set of instructions, we place them in what can be thought of as a vertical column. The list A, B, C, for example, would 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 beginning at the top of the stack.
Elements A, B, and C being added and “popped” from the stack.
We can distinguish between the data (things like signatures, hashes, and public keys) and the instructions (or opcodes). The instructions remove data and do something with it. Here’s a very simple example of what a script could look like:
<xyz> <md5 hasher> <d16fb36f0911f878998c136191af705e> <check if equal>
In red, we have data, and in blue, we have the opcodes. We read from left to right, so we first put the string <xyz> onto the stack. Next up is the <md5 hasher> opcode. This one doesn’t exist in Bitcoin, but let’s say that it removes the top element of the stack (<xyz>) and hashes it using the MD5 algorithm. Then, the output gets added back onto the stack. The output here happens to be d16fb36f0911f878998c136191af705e.
What a coincidence! Our next element to add is <d16fb36f0911f878998c136191af705e>, so now our stack has two identical elements. Lastly, <check if equal> pops two elements off the top and checks if they’re equal. If they are, it adds <1> to the stack. If not, it adds <0>.
We’ve got to the end of our list of instructions. Our script could have failed in two ways – if the remaining element was a zero, or if one of the operators caused it to fail when some conditions weren’t met. We didn’t have any such operators in this example, and we end up with a non-zero element (<1>), so our script was valid. These rules hold true for real Bitcoin transactions, too.
That was just a made-up program. Let’s look at some actual ones now.
Pay-to-Pubkey (P2PK) is incredibly straightforward. It involves locking funds to a particular public key. If you wanted to receive funds in this manner, you would provide the sender with your public key, as opposed to a Bitcoin address.
The very first transaction between Satoshi Nakamoto and Hal Finney in 2009 was a P2PK one. The structure was heavily used in the early days of Bitcoin, but nowadays, Pay-to-Pubkey-Hash (P2PKH) has largely replaced it.
The locking script for a P2PK transaction follows the format of <public key> OP_CHECKSIG. Simple enough. You might have guessed that OP_CHECKSIG checks for a signature against the provided public key. As such, our scriptSig is going to be a simple <signature>. Remember, the scriptSig is the key to the lock.
It doesn’t get much simpler than this. A signature gets added to the stack, followed by a public key. OP_CHECKSIG pops them both and verifies the signature against the public key. If they match, it adds a <1> to the stack. Otherwise, it adds a <0>.
For reasons we’ll elaborate on in the next section, P2PK isn’t really used anymore.
Pay-to-Pubkey-Hash (P2PKH) is now the most common type of transaction. Unless you’re going out of your way to download archaic software, your wallet is likely doing these by default.
The scriptPubKey in P2PKH is the following:
OP_DUP OP_HASH160 <public key hash> OP_EQUALVERIFY OP_CHECKSIG
Before we introduce the scriptSig, let’s break down what the new opcodes will do:
OP_DUP pops the first element, and duplicates it. Then, it adds both back to the stack. Typically, this is done so that we can do an operation on the duplicate without affecting the original.
This pops the first element and hashes it twice. The first round will hash with the SHA-256 algorithm. The SHA-256 output is then hashed with the RIPEMD-160 algorithm. The resulting output is added back onto the stack.
OP_EQUALVERIFY combines two other operators – OP_EQUAL and OP_VERIFY. OP_EQUAL pops two elements and checks if they’re identical. If they are, it adds a 1 to the stack. If not, it adds a 0. OP_VERIFY pops the top element and checks if it’s True (i.e., non-zero). If it isn’t, the transaction fails. Combined, OP_EQUALVERIFY causes the transaction to fail if the top two elements don’t match.
This time, the scriptSig looks like this:
<signature> <public key>
You need to provide a signature and the corresponding public key to unlock P2PKH outputs.
You can see what’s going on in the above GIF. It’s not too different from a P2PK script. We’re just adding an extra step to check that the public key matches the hash in the script.
There is something to note, however. In a P2PKH locking script, the public key is not visible – we can only see its hash. If we go to a blockchain explorer and look at a P2PKH output that hasn’t been spent, we can’t determine the public key. It’s only revealed when the receiver decides to transfer the funds.
This has a couple of benefits. The first is that the public key hash is simply easier to pass around than a full public key. Satoshi launched it in 2009 for this very reason. The public key hash is what we know as a Bitcoin address today.
The second benefit is that public key hashes could provide an additional layer of security against quantum computing. Because our public key is not known until we spend the funds, it’s even more difficult for others to compute the private key. They’d have to reverse the two rounds of hashing (RIPEMD-160 and SHA-256) to get it.
Pay-to-Script-Hash (P2SH) was a very interesting development for Bitcoin. It allows the sender to lock funds to the hash of a script – they don’t need to know what the script actually does. Take the following SHA-256 hash:
You don’t need to know the hash’s input to lock funds to it. The spender, however, needs to provide the script that was used to hash it and needs to satisfy the conditions of that script.
The above hash was created from the following script:
<multiply by 2> <4> <check if equal>
If you want to spend the coins tied to that scriptPubKey, you not only provide those commands. You also need a scriptSig that makes the completed script evaluate to True. In this example, that’s an element that you <multiply by 2> to give a result of <4>. Of course, that means our scriptSig is just <2>.
In real life, the scriptPubKey for a P2SH output is:
OP_HASH160 <redeemScript hash> OP_EQUAL
No new operators here. But, we do have <redeemScript hash> as a new element. As the name suggests, it’s a hash of the script we need to provide to redeem the funds (called the redeemScript). The scriptSig will change depending on what’s in the redeemScript. Generally, though, you’ll find that it’s some combination of signatures and the attached public keys, followed by the (mandatory) redeemScript:
<signature> <public key> <redeemScript>
Our evaluation differs a bit from the stack execution we’ve seen so far. It happens in two parts. The first simply checks that you’ve provided the correct hash.
You’ll note that we don’t do anything with the elements preceding the redeemScript. They’re not used at this point. We’ve reached the end of this mini-program, and the top element is non-zero. That means it’s valid.
We’re not done yet, though. Network nodes recognize this structure as P2SH, so they’ve actually got the scriptSig’s elements waiting in another stack. That’s where the signature and public key will be used.
So far, we’ve treated the redeemScript as an element. But now, it’ll be interpreted as instructions, which could be anything. Let’s take the example of a P2PKH locking script, to which we must provide the <signature> and <public key> that matches a <public key hash> inside the <redeemScript>.
Once your redeemScript has been expanded, you can see that we have a situation that looks exactly like a regular P2PKH transaction. From there, you just run it as you would a normal one.
We’ve demonstrated what’s called a P2SH(P2PKH) script here, but you’re unlikely to find one of those in the wild. Nothing’s stopping you from making one, but it gives you no added benefits and ends up taking up more space in a block (and, therefore, costs more).
P2SH generally comes in handy for things like multisignature or SegWit-compatible transactions. Multisig transactions can be very large in size as they might require multiple keys. Prior to the implementation of Pay-to-Script-Hash, a sender would have to list all of the possible public keys in their locking script.
But with P2SH, it doesn’t matter how complex the spending conditions are. The redeemScript’s hash is always of a fixed size. The costs are therefore passed onto the user(s) who want to unlock the locking script.
SegWit compatibility is another case where P2SH comes in handy (we’ll get into the details of how the transaction structure differs in the next section). SegWit was a soft fork that resulted in a change to block/transaction formats. Because it’s an opt-in upgrade, not all wallet software recognizes the changes.
That doesn’t matter if clients wrap the SegWit script hash in P2SH. As with all transactions of this type, they don’t need to know what the unlocking redeemScript will be.
For a more comprehensive introduction to SegWit, check out A Beginner’s Guide to Segregated Witness.
To understand the transaction format in SegWit, you just need to know that we no longer just have a scriptSig and a scriptPubKey. Now, we also have a new field called the witness. The data we used to keep in the scriptSig is moved to the witness, so the scriptSig is empty.
If you’ve come across addresses beginning with ‘bc1’, those are what we call SegWit-native (as opposed to just SegWit-compatible, which begin with a ‘3’ since 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’ll note that this is the same as the scriptSig from P2PKH. Here, the scriptSig is empty. Meanwhile, the scriptPubKey resembles the following:
<OP_0> <public key hash>
That looks a bit strange, doesn’t it? Where are the opcodes to let us compare the signature, public key and its hash?
We’re not showing additional operators here, because nodes that receive the transaction know what to do with it based on the length of <public key hash>. They’ll calculate the length and understand that it must be run in the same style as a good ol’ fashioned P2PKH transaction.
Non-upgraded nodes don’t know how to interpret the transaction in that manner, but it doesn’t matter. Under the old rules, there is no witness, so they read an empty scriptSig and some data. They evaluate this and mark it as valid – as far as they’re concerned, anyone could 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 probably figure out how this will look, but we’ll run through it anyway. Our witness is what we’d normally put in the scriptSig. In a P2WSH that wraps a P2PKH transaction, for instance, it might look something like this:
<signature 1> <public key>
Here’s our scriptPubKey:
<OP_0> <script hash>
The same rules hold. SegWit nodes read the length of the script hash and determine that it’s a P2WSH output, which is evaluated similarly to P2SH. Meanwhile, old nodes just see it as an anyone-can-spend output.
In this article, we’ve learned a bit about the building blocks of Bitcoin. Let’s summarize them quickly:
Locks funds to a particular public key
Locks funds to a particular public key hash (i.e., an address)
Locks funds to the hash of a script that the recipient can provide
The SegWit version of P2PK
The SegWit version of P2SH
Once you dig deeper into Bitcoin, you begin to understand why it has so much potential. Transactions can be made up of many different components. By manipulating these building blocks, users have a great deal of flexibility when it comes to setting conditions on how and when funds can be spent.