Really Absurd Things

Today we continue chapter 10 about Script, P2SH, and Miniscript.

Are you a designer? If so, perhaps Kraken is looking for you!

Also, if you still want to sign up for my course for next Tuesday, then do so before tomorrow at 18:00 Dutch time at learnbitcoincore.com

(don’t worry if your on-chain payment / channel opening doesn’t clear before the deadline, just send me an email)

Really Absurd Things

Script is a programming language that was introduced in Bitcoin, though it resembles a preexisting language known as Forth. It also seems to have been cobbled together as an afterthought. In fact, a lot of the operations that were part of the language were removed almost immediately, because there were all sorts of ways that you could just crash a node or do other bad things.

Unfortunately, with Bitcoin, you can’t just start with a draft language and then clean it up later. But this only became clear once developers realized the only safe way to upgrade Bitcoin is through very carefully crafted soft forks. Every change has to be backward compatible and not break any existing script. But developers can’t always know the intention of scripts that are already out there, and worse still, as explained above, most scripts are hashed, so they could contain anything.

As a result, it’s been a complete nightmare to make sure upgrades to the Script language don’t do anything surprising or bad. If it turns out that existing nodes can be negatively impacted, e.g. crashed, by some obscure script, developers have to very carefully work around that issue; they have to fix the problem without accidentally making coins unspendable and without introducing new bugs, including in any unknown (hashed) script potentially out there.

Worse still, because Bitcoin is a live system and users can’t be forced to all update at once, an ideal fix should not tip off an attacker as to what the issue is. But at the same time, it’s an open source and transparent system, where changes can’t go through without public justification. This makes Responsible Disclosure very complicated. So it’s really best to go above and beyond to avoid such problems in first place.

The script’s language is diverse enough to allow for weird stuff. If you just want somebody to send money to you, you only need this very simple standard script explained above: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG.

But let’s say you’re collaborating, and you want to do a multi-signature, or multisig. To spend coins, two signatures need to provided, rather than just one. Now you could just use OP_CHECKMULTISIG, but let’s say that didn’t yet exist. Instead, you could take the script for one signature from above and more or less duplicate it, like so: <KEY_A> OP_CHECKSIGVERIFY <KEY_B> OP_CHECKSIG. In this example you’re B, the second key that’s checked (we also don’t bother with hashing the public keys).

Essentially, if you start with those two public keys and two signatures on the stack, and you run the script one instruction at a time, then if A and B put a valid signature on the stack, it’s all good. This would be a poor man’s multisig.

However, a malicious actor could insert an op code called OP_RETURN in the middle: <KEY_A> OP_CHECKSIGVERIFY OP_RETURN <KEY_B> OP_CHECKSIG.

This OP_RETURN code instructs the blockchain to stop evaluating the program — in other words, skipping the signature check for B, your signature.

If you naively looked at this script, you might think that your signature is checked at the end, and so the rest of the script isn’t relevant. If you had a vigilant electronic lawyer (i.e. a person or computer program that does due diligence on transactions), who would properly check that this “smart contract” does what it says it does, they might say, “Careful there, your signature isn’t getting checked.” This hypothetical electronic lawyer should see that OP_RETURN “fine print” and warn you. But the problem is there are countless ways in which scripts can go wrong, which is why we need a standardized way of dealing with these scripts.

In an interview with Bitcoin Magazine, Andrew Poelstra said, “There are opcodes in Bitcoin Script which do really absurd things, like, interpret a signature as a true/false value, branch on that; convert that boolean to a number and then index into the stack, and rearrange the stack based on that number. And the specific rules for how it does this are super nuts.”

This quote exemplifies the complexity of potential ways to mess around with script.

To return to the plate analogy, you’d take a hammer and smash one, and then you’d confuse two and paint one red and then it would still work, if you do it correctly. It’s completely absurd.

So that’s the long and short of the problem with scripts: It’s easy to make mistakes or hide bugs and make all sorts of complex arrangements that people might or might not notice. And then your money goes places you don’t want it to go. We’ve already seen in other projects, famously with the Ethereum DAO hack and resulting hard fork, how bad things can get if you have a very complicated language that does things you’re not completely expecting. But Bitcoin dodged many bullets in the early days, and despite its relative simplicity, it still requires vigilance.

If you can’t get enough of this, watch Andrew Poelstra’s two-hour presentation at London Bitcoin Devs, where he goes on and on and on about the problems in script.