I'm doing my homework by reading the C++ code of the 0.1.0 release of Bitcoin available here. I'm interested in better understanding how to validate transaction 1 of block 170. This transaction has one input, that points to output 0 of transaction 0 of block 9.

script.cpp contains a function EvalScript(). This function is called to execute the following script:

---- Bloc 170 / Transaction 1 / Input 0 / ScriptSig -----
PUSH 304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901
---- Separator -----
---- Bloc 9 / Transaction 0 / Output 0 / ScriptPubKey -----
PUSH 0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3

I had a look at the code for OP_CHECKSIG. This code creates a script that is going to replace the one that lies in ScriptSign of input 0 of a copy of transaction 1 of block 170 later in another function SignatureHash(). The first steps are pretty straightforward:

valtype& vchSig    = stacktop(-2);
valtype& vchPubKey = stacktop(-1);

This puts the signature (DER format) followed by the hashtype (0x01, so SIGHASH_ALL) in vchSig, and the public key prefixed with compression flag (0x04, so no compression) in vchPubKey.

CScript scriptCode(pbegincodehash, pend);

This creates a script that is nothing more than ScriptPubKey of Block 9 / Transaction 0 / Output 0.

And then...:

// Drop the signature, since there's no way for a signature to sign itself

I don't understand this. How could the signature that lies in ScriptSig of input 0 of transaction 1 of block 170 be found in ScriptPubKey of output 0 of transaction 0 of block 9, a previous block?

Of course, I know that this very transaction is a very basic one considering how complex a Bitcoin transaction can be, as explained by Satoshi himself here. But what would be a transaction where this code would be useful?

1 Answer 1


You are correct that the signature would not actually be present in that hash due to the OP_CODESEPARATOR used. Keep in mind that v0.1.0 was very unpolished code and had a lot of bugs and weird behaviour. Not everything makes sense.

To explain for those that want more context, by the time we reach EvalScript(), the script will be of the form:

txin.scriptSig + OP_CODESEPARATOR + txout.scriptPubKey

where txin.scriptSig is from the spending transaction input, and will contain the signature, and txout.scriptPubKey is from the previous transaction UTXO being spent and will contain the public key and OP_CHECKSIG opcode. This is exactly what is written in the question.

Starting from the beginning, the signature will be pushed onto the evaluation stack, then we will hit the OP_CODESEPARATOR so pbegincodehash will be updated to that point in the script, then the public key will be pushed onto the evaluation stack, and finally the OP_CHECKSIG is reached.

Exactly as you say, the signature and public key will then be taken off the stack in reverse order and stored in vchSig and vchPubKey respectively.

scriptCode is then set to the subscript starting at pbegincodehash until the end of the script. In this case, it starts at the OP_CHECKSIG we encountered previously, so scriptCode will simply contain the public key push and the OP_CHECKSIG opcode.

Thus, when scriptCode.FindAndDelete(CScript(vchSig)) is called, it does nothing in this case because vchSig is not part of scriptCode.

However, for the context of why deleting the signature from the script is important for verifying the signatures, let's look a bit further. CheckSig() is called with the signature (vchSig), the public key (vchPubKey), and the scriptCode:

CheckSig(vchSig, vchPubKey, scriptCode, txTo, nIn, nHashType)

which then calls

SignatureHash(scriptCode, txTo, nIn, nHashType)

to generate the hash of the message. The hash is generated by deleting all remaining OP_CODESEPARATORs from the script, setting the scriptSig of all the other inputs to the spending transaction to empty, sets the scriptSig of the current input being signed to scriptCode, and then (ignoring the other signing types) serialises the transaction and hashes it.

Recall that to verify a signature we need the message (which is then hashed), the public key, and the signature itself. The message has to be in exactly the same form as what was originally signed. That is why we have to delete the signature itself from the transaction being signed, because it obviously couldn't have been present at sign time. However, in this case, it does nothing, because there are no signatures after OP_CODESEPARATOR anyway. The clearing of scriptSigs in the SignatureHash() function is more important here because they would all be empty at signing time.

To check this, we can take a look at the SignSignature() function too, which actually generates the signatures. We can see that it passes scriptPrereq + txout.scriptPubKey to the SignatureHash() function above as the scriptCode. scriptPrereq is always empty in this code. So basically scriptCode is always just the scriptPubKey. That matches exactly what we expect.

  • 1
    There are some exotic scriptSigs which contain additional OP_CODESEPARATORs and could have a signature, pubkey, and OP_CHECKSIG that all occur after an OP_CODESEPARATOR, and before another one. In that case, FindAndDelete would be doing something in order to remove the signature from the script. IIRC the TumbleBit protocol uses a construction like this.
    – Ava Chow
    Dec 20, 2021 at 16:26

Not the answer you're looking for? Browse other questions tagged or ask your own question.