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
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.
OP_HASH160
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
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:
e145fe9ed5c23aa71fdb443de00c7d9b4a69f8a27a2e4fbb1fe1d0dbfb6583f1
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. It 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.ย
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)
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)
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:
Script type | Description |
---|
Pay-to-Pubkey (P2PK) | Locks funds to a particular public key |
Pay-to-Pubkey-Hash (P2PKH) | Locks funds to a particular public key hash (i.e., an address) |
Pay-to-Script-Hash (P2SH) | Locks funds to the hash of a script that the recipient can provide |
Pay-to-Witness-Pubkey-Hash (P2WPKH) | The SegWit version of P2PK |
Pay-to-Witness-Script-Hash (P2WSH) | 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.