Bitcoin legacy transactions (and Zcash transparent sends) use "ScriptSigs" to prove authorization. A ScriptSig contains the user's signature andm maybe some other information. They let the protocol check that the tx came from someone allowed to spend those Bitcoin. Most users never interact with them directly, and with the advent of SegWit, they're slowly being phased out of Bitcoin. But it's still worth knowing how they work.
Money in Bitcoin and Zcash is locked to a small program called the "PubkeyScript." The PubkeyScript sets rules for the spender. In order to spend funds, you have to provide a ScriptSig that passes the PubkeyScript program.
Usually these programs are very simple. But they can get more complex.
from riemann.script import serialization
script_text = 'OP_DUP OP_HASH160 deadbeefdeadbeefdeadbeefdeadbeefdeadbeef OP_EQUALVERIFY OP_CHECKSIG'
script_bytes = serialization.serialize(script_text)
serialization.deserialize(script_bytes)
script_hex = serialization.hex_serialize(script_text)
Here's a ScriptSig from a real Bitcoin tx (Zcash ScriptSigs are identical):
4830450221009cf0cc5bd315e5df07da6920482ecd6651f5eadadf2db91b697202b6d0320695022042138453d50658851ec5520a66a326f54b5cb41db53fa3b443d81e2f1be7ad510121030ffc466f7127be5bccf305c8ca58aa5c1cc1531d4f8ad23d87640bb64de34b34
It's kinda ugly.
As the name implies, a ScriptSig is a Script. So we deserialize it using the rules of Bitcoin Script:
PUSH(30450221009cf0cc5bd315e5df07da6920482ecd6651f5eadadf2db91b697202b6d0320695022042138453d50658851ec5520a66a326f54b5cb41db53fa3b443d81e2f1be7ad5101)
PUSH(030ffc466f7127be5bccf305c8ca58aa5c1cc1531d4f8ad23d87640bb64de34b34)
The first push is a signature. The second push is a public key. These are
checked against the PubkeyScript that owns the coins. Bitcoin's 1...
legacy
addresses, and Zcash's t1...
transparent addresses use a standard
PubkeyScript called "pay to pubkey hash" or "P2PKH." That standard script is
OP_DUP
OP_HASH160
PUSH({pubkeyhash})
OP_EQUALVERIFY
OP_CHECKSIG
So we duplicate the provided public key, then we hash the duplicate. Then we check that the hash of the provided public key is the pubkeyhash we expect. If it is, we check the signature. If that passes, then the ScriptSig satisfies the ScriptPubkey.
The second kind of standard address (Bitcoin's 3
addresses and Zcash's t3
)
uses an extra validation step. These are called "pay to script hash" or "P2SH"
addresses, because instead of including the hash of a public key, the funds are
held by the hash of a script. This means that the spender must provide that
script hash.
The standard p2sh script is:
OP_HASH160
PUSH(scripthash)
OP_EQUAL
And here's a parsed ScriptSig that matches it:
OP_0
PUSH(30440220567cce18178b8ecd88178b2d7df906e1e479517b8d81314c3f2da34a8769f79b022063f8da1eb62aae2dc9c88ea90097e83032d04a119f56e41b8af4f1d63a63d37b01)
PUSH(3045022100b6e1fc63c7024a9f14ebe2a0efd6985e8829842652a40e46002f089232a340e4022040934d70b30b56c7c4637789b20b9324071ce8ca8f57e94260a7b19c9812d2f601)
PUSH(52210246ccf4de0c54cc7f3354cdd993c2c50cf965fd82238b89659fbd73a1b4bf05a121024fc59f72272a897fe43803374969f396058152fe4765a8d15216f94624257b1b21022593bc69ecbf3bbcc3c58082267cb49dadaf4ca8dbf1b2297338a9d628c4297653ae)
You might recognize those data pushes starting with 30 as signatures. But what
is that OP_0
doing there? And what is that big blob with 52? Well, get ready
for this because the answer is weird.
The 52...
is a Script within a Script. We call it the RedeemScript because sure,
Gavin decided to for some reason. The standard P2SH behavior is to hash the
RedeemScript, and then check it against the scripthash in the PubkeyScript.
Then, if the hash is correct, we execute the RedeemScript with the rest of the
ScriptSig as arguments.
So let's unpack that RedeemScript:
OP_2
PUSH(0246ccf4de0c54cc7f3354cdd993c2c50cf965fd82238b89659fbd73a1b4bf05a1)
PUSH(024fc59f72272a897fe43803374969f396058152fe4765a8d15216f94624257b1b)
PUSH(022593bc69ecbf3bbcc3c58082267cb49dadaf4ca8dbf1b2297338a9d628c42976)
OP_3
OP_CHECKMULTISIG
Oh hey that looks familiar. That's a standard multisig script with 3 pubkeys!
This one requires 2 signers. So that's why we have 2 signatures up above.
Funnily enough, the OP_0
is because Satoshi wrote an off-by-one bug and
OP_CHECKMULTISIG
needs one more argument than it's supposed to. So we just
pass it a useless 0 because it's easy.
Bitcoin and ZEC are held by PubkeyScripts that can be encoded as Addreses. The PubkeyScript is used to validate the ScriptSig. For P2SH, the ScriptSig must contain a RedeemScript that also gets run. For P2PKH, there's no RedeemScript. All of this is legacy code, and yes, it is a mess. We improved it with SegWit, which is much cleaner.