In the original bitcoin client, the unlocking and locking scripts were concatenated and executed in sequence. For security reasons, this was changed in 2010, because of a vulnerability that allowed a malformed unlocking script to push data onto the stack and corrupt the locking script. In the current implementation, the scripts are executed separately with the stack transferred between the two executions, as described next.

First, the unlocking script is executed, using the stack execution engine. If the unlocking script is executed without errors (e.g., it has no "dangling" operators left over), the main stack is copied and the locking script is executed. If the result of executing the locking script with the stack data copied from the unlocking script is "TRUE," the unlocking script has succeeded in resolving the conditions imposed by the locking script and, therefore, the input is a valid authorization to spend the UTXO.

In this chapter, Andreas states that due to a bug with malformed unlocking scripts, the scripts are now executed separately.

Here is my understanding:

The unlocking script is first executed on the main stack. If the unlocking script is valid, the entire stack is copied and the locking script is then executed.

Is there a reason why the stack is not copied to an alternate stack and then the unlocking script is executed on the alt stack? This way, if the unlocking script is malformed, it would not corrupt the rest of the main stack.


you can find explanation here: https://bitcointalk.org/index.php?topic=5095376.msg49150302#msg49150302

the issue was actually with OP_RETURN. What OP_RETURN used to do is it would skip to the end of the script so whatever was on the stack was interpreted for the final script result. So what you could do was create a scriptSig that was just


which would push true to the stack and skip the rest of the script (which included the scriptPubKey at this time). The interpreter would see that at the end of script execution a true was left on the stack so it would say that the script passed. Obviously this is bad because the nothing in the scriptPubKey was evaluated so none of the spending conditions were actually met.

In fixing this bug, Satoshi disabled many other opcodes, changed OP_RETURN to always cause script execution to fail, and separated the execution of the scriptSig and the scriptPubKey so that the scriptPubKey is always executed.

  • I don't think that this is correct. At the point when the change to separate scriptSig and scriptPubKey execution OP_RETURN was already returning a false, causing the execution to fail. – arubi Jan 11 at 19:19
  • Actually, version 0.3.6 changed the behavior of OP_RETURN and version 0.3.7 had script execution change. Granted, the two versions are two days apart from each other and there is a single commit in between which is only a docs and build system change – arubi Jan 11 at 19:29
  • I think Liu is correct. Before the upgrade for input and output script runs to be 2 consecutive runs (where the 2nd run inherits the stack state of the 1st), they were effectively concatenated and evaluated as a single script run. In such a case, a data push or op_return would relegate the output script to data. – James C. Jan 11 at 21:03
  • But what I'm saying is, it seems that there was no way to abuse op_return at the time when the change was done to execute scriptsig and scriptpubkey separately. op_return was already returning false at that point so prepending it would not help. A PUSHDATA op along with the size of the scriptpubkey seems like it would though! Though this is not the part that is quoted. – arubi Jan 11 at 21:07

Your Answer

