My FeedDiscussionsHeadless CMS
New
Sign in
Log inSign up
Learn more about Hashnode Headless CMSHashnode Headless CMS
Collaborate seamlessly with Hashnode Headless CMS for Enterprise.
Upgrade ✨Learn more

Bitcoin scripts!

For the sake of security

Shashank Karmakar's photo
Shashank Karmakar
·Aug 27, 2021·

7 min read

Bitcoin scripts!

Wait but why?

disclaimer: "sig" is signature; Pubkey is public key; locking script, output script and ScriptPubKey are used interchangeably

Using a programming language, Script, in the transactions makes them very flexible. If the transactions didn’t use a programming language, all use cases would have to be invented up front. The Script language lets people come up with new use cases as they please.

There are several types of them, let's list them chronologically :)

P2PK (pay to public key)

P2PK (Pay To Pubkey) is a script pattern that locks an output to a public key, despite being the simplest script for locking bitcoins to someone’s public key, P2PK is not used as much as the similar (yet more complex) P2PKH script. You’ll most commonly find P2PK in coinbase transactions in the earlier blocks in the blockchain.

ScriptPubKey: <PubKey> <OP_CHECKSIG>
ScriptSig: <sig>

Call Stack:

<sig>
<sig> < pubKey>
<sig> <pubKey> <OP_CHECKSIG> //will verify the signature
<1>

P2PKH (pay to public key hash)

It is similar to P2PK, but the lock contains the hash of a public key instead (and not the public key itself), surrounded by opcodes.

ScriptPubKey:  <OP_DUP> <OP_HASH160> <pubKeyHash> <OP_EQUALVERIFY> <OP_CHECKSIG>
ScriptSig: <sig> <pubKey>

Call Stack:

<sig>
<sig> <pubKey>
<sig> <pubKey> <OP_DUP> //Duplicates the top stack item
<sig> <pubKey> <pubKey> <OP_HASH160> //pubKey is hashed twice, SHA256 + RIPEMD160, why twice? we'll see later
<sig> <pubKey> <pubKeyHash> <pubKeyHash>
<sig> <pubKey> <pubKeyHash> <pubKeyHash> <OP_EQUALVERIFY> // verifies first 2 items equal or not, if equal then script continues, else halts
<sig> <pubKey> <OP_CHECKSIG>
<1>

Advantages over P2PK: You can make the public Key

  • Safer by adding a checksum (to detect errors). encode.jpg
  • Shorter by converting the public key to base58, with address starting with '1'

but if you take the 130 characters pubkey add prefix and checksum, and encode it with base58 it will still be pretty darn large, 95 characters, solution is to hash the pubkey with SHA256 and then RIPEMD160, why twice? double security and also gets converted to 160 bits , which means 40 character in hexadecimal, adding checksum(8 characters) and prefix(2 characters) for identification of the version number it boils down to 50 characters, meaning 25bytes, which means 200 bits, upon base58 encoding gets converted to 34characters, and these 34 characters become the bitcoin address for p2pkh.

How will sender's wallet extract pubKeyHash from the address and inject it inside output script?

The sender will scan the QR code will receive the base58 address, 19g60...PD, base58 decode it, remove version number, 00 for p2pkh, remove and verify checksum, if checksum verification fails transaction should fail, else take the remaining address, that is your PKH to be injected in output script or scriptPubKey.

decode.jpg

What about compressed publicKeys?

If you base58 encoded a compressed public key you would get an address that is 51 characters long (as opposed to the 34 characters you get by hashing it beforehand)

P2MS (pay to multisig):

For example, you could create a P2MS script that includes the public keys of 3 different people, but only 2 of those people would need to provide their signatures to spend the bitcoins, it was introduced in BIP11

// example of 2 of 3 multisig output you can create m of n on your own
ScriptPubKey:  <OP_2> <pubKey1> <pubKey2> <pubKey3> <OP_3> <OP_CHECKMULTISIG>
ScriptSig: <OP_0> <sigA> <sigB>

This opcode OP_CHECKMULTISIG actually has a bug where it pops one extra element of the stack than it needs to. So to avoid an error, we add a dummy value (typically OP_O) at the start of the scriptSig.

call stack:

<OP_0> <sigA> <sigB> <OP_2> <pubKey1> <pubKey2> <pubKey3> <OP_3> <OP_CHECKMULTISIG>
// OP_CHECKMULTISIG Pops off N, here 3, and then pops that number of public keys of the stack.
// Pops off M, here 2, and then pops that number of signatures of the stack.
// verifies signature with pubKey, so make sure you the pubkeys in specific order

Now let's address the elephant in the room apart from providing multisig held fund, it is just a hassle, how?

  • it does not have an address type, unlike P2PKH.
  • The UI of the wallet needs to updated so to create the locking script, this will not only annoy the sender but also sender doesn't need to know how the recipients money is protected.
  • only limited to 3 public keys.

The gorilla in the room

Now there are some issues with all but one stands out, for the transactions the fee needs to be paid per byte of memory, now in both P2PKH and P2MS, the locking script or the scriptPubKey of the sender is large, but this isn't fair since the receipt is the one claiming the fund he should paying the fee not the sender, hence we need something better:)

P2SH

Introduced in BIP16, these addresses start with '3' (unlike p2pkh which start with '1') are also encoded with base58 and software recognizes it cause its version number is 05 unlike p2pkh with version number 00.

 ScriptPubKey: <OP_HASH160> <20-byte-hash-value> <OP_EQUAL>
ScriptSig: <OP_0> <sigA> <sigB> <serialized script>
// this serialized script can be any script, even multisig, but in serialized form

The sender will just scan the QR code and the 20 byte hash is registered in the it's locking script, now the sender will just be paying just the 20 byte hash owner he has no idea what the preimage script of the hash is, the serialized script can be multisig too but in serialized form. A multisig P2SH scriptsig will look like <OP_0> <sigA> <sigB> [<OP_2> <pubKey1> <pubKey2> <pubKey3> <OP_3> <OP_CHECKMULTISIG>]

Call Stack for node knowing p2sh verifying this transaction:

/*
First phase
Standard Execution
It will be simple almost p2pk type code execution
*/
<sigA>
<sigA> <sigB>
<sigA> <sigB> <serialized script> // psss, this stack is saved and an instance of it is copied this copied instance will be used later in phase 2
<sigA> <sigB> <serialized script> <OP_HASH160> // hashes the serialized script
<sigA> <sigB> <20-byte-hash-value> <OP_EQUAL> // if equal returns true else false
/* whenever you see an opcode with VERIFY at the end , OP_EQUALVERIFY, they don't return anything, they continue executing if the conditional matches, else the script gets halted*/
/*
Second phase
Serialized Script Execution
It will like the p2ms style execution
Now after phase one we take the serialized script and deserialize it, 
kinda like a serialized binary tree in array to a full fledged deserialized version of it

just refer to the multisig script execution above
*/

Forward compatibility

What if the node verifying the transaction hasn’t upgraded their software to the bleeding-edge version that supports verifying p2sh payments? The developers made this forward-compatible, meaning old software won’t reject these new transactions.

call stack for a node not knowing p2sh

<sigA>
<sigA> <sigB>
<sigA> <sigB> <serialized script> // this time stack not copied cause node doesn't know p2sh
<sigA> <sigB> <serialized script>
<sigA> <sigB> <serialized script> <HASH_160>
<sigA> <sigB> <20-byte-hash-value> 
<sigA> <sigB> <20-byte-hash-value> <20-byte-hash-value>
<sigA> <sigB> <20-byte-hash-value> <20-byte-hash-value> <OP_EQUAL>
<sigA> <sigB> <1>
// at top there is true, the transaction gets verified, ingenious !

To summarize advantages over previous methods:

  • cheaper transaction cost for the sender, since the output script size has been reduced, and fee responsibility is transferred to the recipient.
  • little more privacy for the recipient.
  • To make the bitcoin program itself run as fast as possible, all of the unspent outputs from transactions (UTXOs) are stored in RAM on your computer, since you are using smaller p2sh scripts than long p2ms scripts.
  • you can use multisig locks with up to 15 public keys.

but yes p2sh script overall takes more space in blockchain compared to the other scripts. For example, a complete 2-of-3 multisig script using the simple P2MS pattern takes up 253 bytes in the blockchain:

<OP_0> <sigA> <sigB>       <OP_2> <pubKey1> <pubKey2> <pubKey3> <OP_3> <OP_CHECKMULTISIG>
/*
scriptSigSize    +    scriptPubKeySize

  148bytes        +           105bytes         =        253 bytes
*/

On the other hand, a complete 2-of-3 multisig script using P2SH takes up 278 bytes in the blockchain:

<OP_0> <sigA> <sigB> {<OP_2> <pubKey1> <pubKey2> <pubKey3> <OP_3> <OP_CHECKMULTISIG>}             <OP_HASH160> <20-byte-hash-value> <OP_EQUAL>
/*
scriptSigSize    +    scriptPubKeySize

(148 + 107)bytes   +   23 bytes        =       278 bytes
*/

So the extra step of locking bitcoins to the hash of a script adds an extra 25 bytes to the overall script, but p2sh not only provides you with a custom locking script but also multisig p2sh have their addresses, and manifold of other advantages, so.........

IMG_20210830_033617.png

So more privacy and flexibility, lesser fees for sender, life seems good, right? or so they thought, but let's leave transaction malleability, O(n^2) signature verification, adoption of segwit, etc, for another time.

Hey, I am Shashank and when I am not writing code and solving puzzles I write about algorithms, bitcoin and lightning network, so stay tuned ;)