0

It is clear to me why it's potentially unsafe to only use a check on == in scriptPubKey. For one, not verifying a signature in the redeem script means that someone could impersonate the redeemer. From the Bitcoin developer guide:

This lets the pubkey script verify that Bob owns the private key which created the public key.

Bob’s secp256k1 signature doesn’t just prove Bob controls his private key; it also makes the non-signature-script parts of his transaction tamper-proof so Bob can safely broadcast them over the peer-to-peer network.

Why would it be unsafe to only use a check on V(sigA, pkA, m={txprev_txid, txprev_outid, txprev_scriptPK, tx_scriptPubKey, tx_amount}_skA) == 1? It seems like this is what we're doing in multisig. I.e., have a ScriptSig that consists only of (a) signature(s) instead of sig and pk like in P2PKH.

Why don't we just check signatures instead of checking both pkhash and a signature?

1

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:

  1. That it holds the correct private key
  2. 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:

  1. 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.
  2. The serialized redeem script is hashed and validated against the locking script
  3. 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.

1

In an input spending a P2PKH output, if you were to only check that the signature is a valid signature with the public key in the input, then an attacker could use their own public key and produce a valid signature and thus a valid transaction under this model. The public key is not retrieved from the output script nor is it provided by the output; rather it is provided in the input. Thus you must ensure that the provided public key is indeed the correct public key, hence the check that it's hash matches the hash provided in the output.

In multisig, the public keys are provided in the input, in addition to the signatures. They are in the redeemScript. To avoid the same issues (of the attacker providing their own public keys), the redeemScript is hashed and the hash is in the output script. There is a comparison there to ensure that the redeemScript is the correct script.

There is an output type where you do just provide a signature. This output type is a script where the public key is in the output so there is no need to provide the public key in the input. Only the signature is needed. This is known as a Pay to Pubkey (P2PK) output. These are not widely used because public keys are large and this makes it harder to give them out.

  • I see. I think I was confused by the Bitcoin Developer Guide. I understand that P2SH multisig has the pk's in the redeemScript input. I was confused by this section, which makes it look like the scriptSig only contains signatures: Pubkey script: <m> <A pubkey> [B pubkey] [C pubkey...] <n> OP_CHECKMULTISIG; Signature script: OP_0 <A sig> [B sig] [C sig...]. Does this mean that this scriptPK and scriptSig are not actually used in practice? But rather the P2SH multisig? – acnalb Oct 21 '18 at 0:07
  • In practice P2SH multisig is used. Having the multisig in the output (known as bare multisig) has almost never been used. – Andrew Chow Oct 21 '18 at 0:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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