DEV Community

Peter Tyonum
Peter Tyonum

Posted on

Generating and Working With ScriptPubKeys in Bitcoin Transactions

Introduction:

Bitcoin transactions involve locking funds in scripts, which can only be spent if those locking conditions are met. The part of the script that expresses these locking conditions are called ScriptPubKeys. On the other hand, the part that provides unlocking scripts to satisfy the locking conditions is referred to as ScriptSig for legacy transactions, and ScriptWitness for SegWit Transactions. These scripts are evaluated by a stack-based language called Script. This article will mainly focus on ScriptPubKeys.

As mentioned previously, ScriptPubKeys are scripts that embody the locking conditions of bitcoins in a Bitcoin transaction. They are a crucial element of a Bitcoin transaction that specifies the requirements that must be met before an unspent transaction output (UTXO) is used. The ScriptPubKey, combined with the Amount, creates an output of a Bitcoin transaction. Like every other script, ScriptPubKeys are made up of operators and data. During the encoding of a transaction, both data and the operands are represented in hexadecimal format. You can find the hex representation for all operands used in Bitcoin Scripts here.

When encoding transaction data, ScriptPubKeys and ScriptSig are typically preceded by the length of the script. To represent the length of a script whose length ranges between 1 to 75 bytes, a single byte is used, with hexadecimal values ranging from 0x01 to 0x4b. If the script's length is between 76 and 255 bytes, it is preceded by a PUSHDATA1 operand. Similarly, if the length is between 256 and 520 bytes, it is represented by PUSHDATA2.

During the evaluation of transactions, ScriptSigs are evaluated first, followed by ScriptPubKeys. If both evaluate to true, the value is transferred to a new destination. So how are ScriptPubKeys derived?

Deriving ScriptPubKeys

One of the simplest ways to obtain the ScriptPubKeys is through the Bitcoin Command Line Interface (CLI). This method is ideal for development and testing purposes. Once you generate an address using the bitcoin-cli getnewaddress command or you have an address available, you can check the address information to obtain the ScriptPubKey value using the bitcoin-cli getaddressinfo <address> command. This command will produce various outputs, including the ScriptPubKey value, highlighted in the screenshot below.

ScriptPubKey using bitcoin-cli

Now, let’s look at how we can derive ScriptPubKeys outside the fancy world of Bitcoin cli for all types of standard scripts.

Pay to Public Key (P2PK)

Although the P2PK scripts have been depreciated in Bitcoin core, standard P2PK ScriptPubKeys are derived using the following script combination:

<publickey> OP_CHECKSIG

The above script combination is then encoded in hex format. When using ECDSA-compressed public keys, the above may likely be 35 bytes long. This is because the public keys are 33 bytes long, a single byte for OP_CHECKSIG (ac in hex) and another byte to indicate the total length of the P2PK ScriptPubKey, resulting in a total of 35 bytes long script. 

Pay to Public Key Hash (P2PKH)

P2PKH used in P2PKH transactions are now considered legacy transactions. They have been replaced by newer Segwit P2WPKH outputs that are non-malleable and smaller in size. Nonetheless, the P2PKH scripts have some advantages over P2PK. For instance, the public key hash is shorter than the bare public key, resulting in lower storage requirements. To encode a P2PKH script, the below procedure is followed:

OP_DUP OP_HASH160 <pubkey hash> OP_EQUALVERIFY OP_CHECKSIG

To obtain the public key hash, take SHA256 of the public key, and then RIPEMD160 of the resulting hash (HASH160(publickey)).

Pay to Multisig (P2MS)

The P2MS standard enables multiple parties to manage their assets collectively by enlisting public keys (n) and the minimum number of signatures (m) required to unlock funds locked in its ScriptPubkey. This reduces the risk of a single point of failure. Although P2MS produces the longest script, it ensures safety by requiring multiple signatures. The P2MS standard is defined as follows:

<m> <n pub keys> <n> OP_CHECKMULTISIG

Here, 'm' represents the required signatures, 'n pub keys' refers to all the public keys, and 'n' denotes the total public keys.  

Pay to Script Hash (P2SH)

P2SH enables complex conditions to be embedded in a script. The script is commonly used in multisig transactions where several parties provide public keys used in deriving the script hash. P2SH ScriptPubKeys are generated using the following formula:

OP_HASH160 <script Hash> OP_EQUAL

To unlock the above, you need to present the custom secret which was hashed to obtain the script hash (Redeem script).

Pay to Taproot (P2TR)

P2TR is a Bitcoin script that locks bitcoins to a script that can be unlocked using a public key or a Merkelized Alternative Script Tree (MAST). P2TR scriptpubkeys often take the shape of a single Schnorr public key. They are however derived from a combination of other public keys. Outputs locked in a P2TR are spent either by a ScriptSig for the public key or any of the scripts within the Merkle tree. To construct a locking condition for P2TR, a few steps are required. First, Schnorr public keys are derived, which are 32 bytes long. Next, P2PK scripts are derived using the formula:
<len> + <pubkeys> + OP_CHECKSIG
After that, Tapleaves are calculated, and then their Taptweak is computed. If you would like to learn more about how to derive P2TR ScriptPubKeys, you can find more detailed information here.

Segwit?: You may be wondering if Segwit implementation has caused any changes in the ScriptPubKeys of the standard scripts mentioned above. The answer is no, Segwit did not introduce any new structure to the scripts. Instead, it relocated the unlocking scripts to the witness section of the transaction data.

There is a unique ScriptPubKey known as NULL DATA, used primarily to store data on the blockchain. The format for this type of ScriptPubkey is:
OP_RETURN <data>
when the Script execution comes across OP_RETURN, it will halt and return false.

Validating ScriptPubkeys

During development, you might want to check that a derived ScriptPubKey is valid. The easiest way to do this by using the Bitcoin-cli. Simply pass the script to the decodescript <script> command and it will return the details of the script.
Example below:

Validating ScriptPubKey using bitcoin-cli

Pls note that in the above example, bcr is an alias for bitcoin-cli -regtest
In the above screenshot, we are encoding a P2PK script as displayed on the type. It has a pubkey of 039ecd8fee6e102eae1a71f99bc14694a51bba8938c6d5785c81012bd4a364a901 and an OP_CHECKSIG which conforms to the P2PK stated above. 

How to use ScriptPubKey in a Bitcoin Transaction

After constructing a ScriptPubkey using any of the above standard scripts, you might want to include it in a transaction. You do this by calculating its length and prefixing it in hex format.  

Summary: This article discussed various methods to express standard locking conditions in a transaction. We covered how to derive ScriptPubKeys using the Bitcoin command line by providing an address. Additionally, we explored different standard scripts that can be implemented when working on a transaction. We also learned how to verify the accuracy of a derived script and concluded by examining how these scripts can be utilized when constructing a transaction. Thank you for reading and I welcome your feedback.

References:

Top comments (0)