P2SH and Segwit scripting: Bitcoin, How Could You Do This?

Today, I am very disappointed in Bitcoin.

I know that we engineers will sometimes take shortcuts, and do things that we are not proud of, in the name of “getting it done and shipped”. In my 38 years as a working engineer, I know I have taken some shortcuts when faced with a deadline, things that I look back on and say “I have to make sure folks know about that so they don’t spend a month figuring it out after I am gone”. You know, an RC to stop the oscillation, when a circuit redesign was better. or telling the driver SW writer to just put in a “if you see this then just set up this reg in this way and move on” workaround.

These things are maintenance nightmares. And they are worse in SW where it just makes hard-to-understand code impossible-to-understand.

I’ve been trying to internalize Bitcoin scripting. This is the capability where, when you want to spend a coin, you have to provide a script that, when added to a script that came with the coin, will prove you own that coin, and therefore can spend it. It turns out that Bitcoin has a fairly robust scripting language that could allow you to do all sorts things to set conditions for a coin to be allowed to be spent. Except, its not a Turing complete language. Meaning it does not have loops. This is required to avoid infinite looping when executing a script. All scripts must run to completion.

This is what Ethereum was essentially invented to fix. It has a Turing complete language, and uses the concept of gas to protect against infinite loops. When you run out of gas, the program stops executing. Its a simple, elegant solution.

The first thing that I found interesting about Bitcoin scripting is that it comes in two parts. The first part is provided as part of something the SENDER does. They attach a “scriptPubKey”, which defines part of the script that will run when I want to spend that coin. So you could end up “owning” a coin that is really difficult, or impossible, to spend. For example, they could send it as a “2 of 3 multisig”. So I would need another person to sign when I wanted to spend that coin. Given the extent of the scripting language, you could theoretically set up all sorts of scenarios to encumber the spending of a coin.

The second part I found interesting is that I have to provide the other part of the script when I want to spend that coin. This is called the “scriptSig” as it usually includes one or more digital signatures (usually ECDSA cryptographic signatures). My part of the script is executed first, then the scriptPubKey is executed using the stack that’s been created by my script. Again, given the robustness of the scripting language, you could see all sorts of scripts being enabled to cover all sorts of possible scenarios.

However…

Turns out that early on in Bitcoin development, they decided to limit the scriptPubKey possibilities to a “standard” few. You could create a non-standard script, but not many nodes or miners would support or execute it. Thus you’d have all sorts of issues spending that coin.

From what I’ve read so far, its keyed off the scriptPubKey. If that follows one of the recognized patterns, then its considered a standard transaction. Early on there was P2PKH, pay to public key hash. That scriptPubKey looks like this:

OP_DUP, OP_HASH160, hash(pubkey), OP_EQUALVERIFY , OP_CHECKSIG

And there are a few others that are considered “standard. Go watch Andreas’ excellent YouTube video on Bitcoin scripting if you don’t understand this.

in the Bitcoin github, in bitcoin/src/script/standard.cpp, you find this block near the top:

const char* GetTxnOutputType(txnouttype t)
{
switch (t)
{
case TX_NONSTANDARD: return “nonstandard”;
case TX_PUBKEY: return “pubkey”;
case TX_PUBKEYHASH: return “pubkeyhash”;
case TX_SCRIPTHASH: return “scripthash”;
case TX_MULTISIG: return “multisig”;
case TX_NULL_DATA: return “nulldata”;
case TX_WITNESS_V0_KEYHASH: return “witness_v0_keyhash”;
case TX_WITNESS_V0_SCRIPTHASH: return “witness_v0_scripthash”;
case TX_WITNESS_UNKNOWN: return “witness_unknown”;
}
return nullptr;
}

Note that there are only a handful of TX types recognized. And the thing I learned today that is disappointing, is that depending on the type, it will “do” different things, other than just execute the script. This became clear when I was trying to understand P2SH, pay to script hash. P2SH is Bitcoin’s way of allowing more complex scripts for encumber spending a coin. But to fit it into the “standard” model, they decided to make it look similar to other standard transaction types (even though a new one was added from BIP16 to support this). The scriptPubKey looks like this:

OP_HASH160, hash(script), OP_EQUAL

Somewhat similar to the P2PKH one above. In the scriptSig you provide the hex of the script that you want executed. the above script will hash that hex script, and compare it with the one in the scriptPubkey, and leave a true on the stack if they are equal. BUT HOW DOES THE SCRIPT GET EXECUTED????

The answer turns out to be “we recognize the script type and do something based on that” In particular, it will take that hex script and go treat it as a script, and execute it after its done with the first script execution! NOT REALLY OBVIOUS IS IT!

having been brought up in the world of trying to design maintainable hardware and code, this flies against every bone in my body. “it works the same as other things, except when it doesn’t”. The more you do of this type of thing, the more CRUD you add until eventually thing reach the point where you can’t change anything for fear of breaking the thing you did not realize was hacked into the code. Apparently, there was another proposal that used a new opcode OP_EVAL, which is more like a model you expect to find for taking a block of hex and treating it like its code. I found a quote from Pieter Wuille, who also worked on segwit on stackexchange about that decision:

“P2SH can only be invoked using the exact magic pattern described in BIP16 in the scriptPubKey and not using any variant of it, nor recursively inside the P2SH redeemScript itself.

Earlier proposals (see OP_EVAL) existed that did allow much more flexibility, but it was hard to give strong guarantees about execution time and memory in such a model.
answered Mar 16 ’16 at 10:49
Pieter Wuille”

You can now see how a young Vitalik B would look at this situation and say “there has to be a better way” and was spurred to invent Ethereum. Ethereum is pretty much the polar opposite of Bitcoin. It allows you to do almost anything in its scripts (which are really contracts). But with power comes danger. You can write scripts to do almost anything, but that also makes them incredibly hard to design  to be hack proof. So maybe the bitcoin approach is one where it can’t easily be extended. but the things it does can be proven to be reliable and bug free. Except that conflicts with my previous assertion that adding CRUD makes it harder to write bug free code. As of this time, there are only 7 standard types recognized (look at the block quoted above). And 2 of them are segwit related, which took the core team 2 years of arguing to add. However, if you are like me and believe that crypto currencies will change the world for the next century, then at the rate of one new “standard” type every two years, is 5 new types over the next century. What are the chances of maintaining that code base effectively?

So what are my conclusions from all this? it makes my belief that solutions like Ethereum are the true wave of the future be even stronger. Bitcoin is not going away any time soon. but with its limited flexibility, i can;t see it being more than a store of value in the long term. That’s not necessarily a bad thing, but not where I think the innovation is going to happen.

On the other hand, there is a great quote from Andreas in the above mentioned Bitcoin scripting video on youTube, which goes something like this: “never understimate the ingenuity of good engineers, driven by smell of money!”


some clips from various articles:

https://bitcoin.stackexchange.com/questions/49372/whats-the-purpose-of-scriptsig-in-a-segwit-transaction:

Here’s an example of a SegWit transaction that also contains data in the ScriptSig field:

{
  "txid": "954f43dbb30ad8024981c07d1f5eb6c9fd461e2cf1760dd1283f052af746fc88",
  "hash": "a5947589e2762107ff650958ba0e3a3cf341f53281d15593530bf9762c4edab1",
  "size": 231,
  "vsize": 148,
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid": "56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115",
      "vout": 4,
      "scriptSig": {
        "asm": "002001d5d92effa6ffba3efa379f9830d0f75618b13393827152d26e4309000e88b1",
        "hex": "22002001d5d92effa6ffba3efa379f9830d0f75618b13393827152d26e4309000e88b1"
      },
      "txinwitness": [
        "3044022038421164c6468c63dc7bf724aa9d48d8e5abe3935564d38182addf733ad4cd81022076362326b22dd7bfaf211d5b17220723659e4fe3359740ced5762d0e497b7dcc01", 
        "21038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acac"
      ],
      "sequence": 4294967295
    }
  ],

If the txinwitness is the new location for the “unlocking script” section for a vin (as opposed to the old scriptSig field), then why would this transaction still have data in its scriptSig field?

The reason is that for now, no native segwit address format exists, so transactions cannot be sent directly to segwit outputs. A proposal for native segwit addresses was created, but it won’t be usable until all senders and receivers support it.

Instead, segwit-inside-P2SH is used, where outputs go to a BIP16 output with as hash script the hash of the witness program.

To spend such outputs, we must stay compatible with BIP16, and thus a scriptSig that reveals the full witness program is required. That is what you’re seeing in this transaction: a BIP16 scriptSig, which reveals a witness program, which on its turn causes inspection of the witness data for the real verification.

  • Are there any proposals for a native P2WPKH address format? Will such transactions be valid when segwit activates in 0.13.1? Or does all segwit have to be contained in P2SH for now? – pinhead Nov 5 ’16 at 23:19
  •  There is no address format for native P2WPKH, but it is included in BIP141, so such outputs are fully consensus valid and standard, there is just no way to ask a sender to construct such an output yet. I’m working on a proposal, though. – Pieter WuilleNov 6 ’16 at 0:20

From bitcoin stackexchange: Whenever a spend (a) validates using normal rules and (b) has a locking script that satisfies a specific pattern, defined in BIP16, an addition P2SH-specific execution follows, whose validation result is what matters.

This same approach is used for a much more recent network rule improvement, called SegWit, specified in BIP141, which is expected to be activated on the network around August 22nd 2017

Also from bitcoin stackexchange: My question in other words has to do with step 6. “Now our redeemScript can be evaluated”: how, since it has been removed from the stack…
A: I’ve been hunting for this information myself, and had to dig to the bitcoin source code to find the answer – couldn’t find it anywhere else.

There is a hardcoded logic in bitcoin for P2SH validation:

The stack is backed up between the steps 2 and 3 (generally – between scriptSig and scriptPubKey execution).

After both scripts are executed and verified, the stack is restored from the earlier backup, the redeem script is popped from the stack, deserialized, executed and verified. This is done by internal bitcoin code, not by any script.

Corresponding source code: https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp

Stack is backed up at 1412, and restored at 1448, where all the magic begins.

 

Leave a comment