Let's start from the beginning.
p2pk
If you look at the initial blocks on the chain, you'll notice that the coinbase transaction output goes to a pay-to-pubkey lockscript (also known as output or encumberment)
A p2pk locking script is simply PUSH <pubkey> OP_CHECKSIG
. To spend this output, one simply needs to provide a valid signature. The scriptsig would contains PUSH sig
, which when combined with the locking script produces PUSH sig PUSH <pubkey> OP_CHECKSIG
. If the signature provided is valid for the given pubkey, you can prove both ownership of coins and intent to spend in a single move.
p2pkh
p2pk was superseded (perhaps not the correct term, since Bitcoin 0.1 contained support for both p2pk and p2pkh) by pay-to-pubkey-hash. This has a couple of advantages over the vanilla p2pk. For one, it reduced the size of the locking script. Since the utxo set must contain the locking script for validation purposes, this results in direct space savings. For another, hashing the public key adds a layer of protection against any future ecdsa key recovery attacks that may be developed, as you would also need to break the HASH160 operation to recover the public key first.
Using p2pkh comes with some additional complexity. The locking script now takes the form of OP_DUP OP_HASH160 PUSH <PubkeyHash> OP_EQUALVERIFY OP_CHECKSIG
. Since we no longer have the actual public key in the script, an unlocking script must prove two things:
- That it holds the correct private key
- That it intends to spend the coins
For 1, we must prove that the public key hash in the script corresponds to the public key hash of the key used to perform the signature. For 2, we must verify that the signature is valid against that public key.
To achieve this, the unlocking script takes the form PUSH sig PUSH pubkey
. When combined with the locking script, this yields PUSH sig PUSH pubkey OP_DUP OP_HASH160 PUSH <PubkeyHash> OP_EQUALVERIFY OP_CHECKSIG
Now, during evaluation, the pubkey is duplicated. The duplicate is hashed and compared against the hash stored in the locking script. If the hash is valid, the signature is validated against the provided public key. This flow ensures that the same public key is used signature verification and the comparison against the hash in the locking script, thus fulfilling both requirements.
p2sh
pay-to-script-hash was developed to provide a standardized way of using more advanced bitcoin scripts. For this example, let us focus on the multisig p2sh. A typical, 1 of 2 multisig p2sh output's locking script will be similar to OP_HASH160 PUSH <hash> OP_EQUAL
. This doesn't contain any public keys, or even a signature checking op code, so what's going on here?
The secret lies in the redeem script. Each p2sh address is backed by a redeem script, and the hash
value in the locking script is a hash of this redeem script.
When spending from a p2sh address, you must provide an unlocking script that validates against the redeem script, and the redeem script itself. For our 1of2 multisig address, a redeem script looks like OP_1 PUSH pubkey1 PUSH pubkey2 OP_2 OP_CHECKMULTISIG
. This entire script is hashed for the locking script. Note that since the pubkeys contained in this script are already hashed as part of the entire redeem script, we do not need to hash them separately, as we do with p2pkh.
When spending the output, we would then provide: OP_0 PUSH sig PUSH redeemscript
. This results in a final script of OP_0 PUSH sig PUSH redeemscript OP_HASH160 PUSH <hash> OP_EQUAL
. During evaluation:
- The unlocking script and locking script are combined. This results in the signatures and the serialized redeemscript being pushed to the stack. Note that since
PUSH redeemscript
treats the redeemscript as normal data, the op codes inside the redeem script are not interpreted as op codes in this step.
- The serialized redeem script is hashed and validated against the locking script
- The signatures are validated against the popped stack, which contains the serialized redeem script without its push op code, and thus interprets it correctly as a bitcoin script.
Following this order of operations provides the same guarantee as the p2pkh output - That the transaction intends to spend the coins, and that the keys involved are the same keys that were committed to during the coins' locking.