How to construct a scriptPubKey
Unspent transaction outputs (UTXO) in bitcoin are locked to a scriptPubkey. Every bitcoin transaction output has a scriptPubkey which specifies how that transaction output can be unlocked or spent.
Understanding Bitcoin transactions and their associated scriptPubkey types is crucial if you want to work with Bitcoin. If you’re not already familiar with these concepts, I recommend checking out the following resources:
https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch06.asciidoc
https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch07.asciidoc
https://medium.com/@sadeeqcode/why-bitcoin-address-is-not-like-an-account-number-e5a5261a0605
Once you understand Bitcoin transactions and scriptPubkey types, you can start working with code. However, if you’re not familiar with these concepts, it’s essential to take the time to learn the basics before proceeding.
How do you construct a scriptPubkey?
In this article, Javascript will be used to construct various bitcoin transaction output scriptPubKeys from P2PK, P2PKH, P2SH, P2MS, P2WPKH, and P2WSH, if you have basic programming knowledge you will be able to understand the Javascript syntax and follow along with this article, also the principles can be used to construct the scriptPubKeys in any other programming language.
1. P2PK. (Pay to Public Key)
To construct a P2PK you will need a public key either compressed or uncompressed, a P2PK is in the format
<public key> OP_CHECKSIGOP_CHECKSIG is a bitcoin script opcode or command that is defined by the bitcoin script syntax and a hexadecimal public key with OP_CHECKSIG is a P2PK scriptPubKey.
Example.
Given an uncompressed public key
“04ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84c”
The scriptPubKey is
04ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84c OP_CHECKSIGThe hex value of OP_CHECKSIG according to the bitcoin script syntax is “ac”, hence you concatenate the public key and the OP_CHECKSIG hex value.
Note: You can find the reference to the hex values of OP_CHECKSIG and other bitcoin script opcodes here https://en.bitcoin.it/wiki/Script
const opCheckSig = "ac";
const publicKey = "04ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84c";
const scriptPubKeyP2PK = publicKey + opCheckSig;
console.log(scriptPubKeyP2PK)
//04ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7//aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac2. P2PKH (Pay to Public Key Hash)
To construct a Pay to Public Key Hash you will need a Public Key Hash. A Public key hash is the result of a double hash of a public key first with sha256 second with RIPEMD160, which is referred to in the bitcoin codebase as “HASH160” for short.
The format of a P2PKH is below
OP_DUP OP_HASH160 <public key hash> OP_EQUAL_VERIFY OP_CHECK_SIG.Example with a Public key hash “12ab8dc588ca9d5787dde7eb29569da63c3a238c”
We construct a P2PKH below
OP_DUP OP_HASH160 12ab8dc588ca9d5787dde7eb29569da63c3a238c OP_EQUALVERIFY OP_CHECKSIGThere is a Bytes to push after OP_HASH160 resulting in scriptPubKey
OP_DUP OP_HASH160 <bytes to push> 12ab8dc588ca9d5787dde7eb29569da63c3a238c OP_EQUALVERIFY OP_CHECKSIGThe Hex value of OP_DUP is “76”, OP_HASH160 in hex is “a9”, Bytes to push in hex is “14”, while OP_EQUALVERIFY in hex is “88”.
const opDup = "76";
const opHash160 = "a9";
const bytesToPush = "14"
const opEqualVerify = "88";
const publicKeyHash = "12ab8dc588ca9d5787dde7eb29569da63c3a238c"
const scriptPubKeyP2PKH = opDup +opHash160 + bytesToPush + publicKeyHash + opEqualVerify + opCheckSig;
console.log(scriptPubkeyP2PKH)
//76a91412ab8dc588ca9d5787dde7eb29569da63c3a238c88ac3. P2MS (Pay to MultiSig )
Pay to multi sig are scriptPubkeys used to lock bitcoin in multiple public keys from 1 to 3, which will require 1 to 3 signatures to unlock the funds depending on the value locking added during construction
P2MS is in the format
(N is the number of public keys in the multi signature output, which can be 1 to 3
M represents the number of signatures that will be required to unlock the output, and should always be less than or equal to M).
M <public keys> N OP_CHECKMULTISIGAn example is a 1 of 3 Multisig scriptPubKey (a scriptPubKey which can be used to lock unspent bitcoin transaction output with three public keys, whereby a single signature from one of the three public keys can unlock the output)
The scriptPubkey format of the above example is
1 <pubkey1 pubkey2 pubkey3> 3 OP_CHECKMULTISIGNote: bitcoin script has a bug that requires 0 to be added before the actual script.
The above script is actually
0 1 <pubkey1 pubkey2 pubkey3> 3 OP_CHECKMULTISIG.Given pubkey1 as “0319c337fa76df71ecb4744c8bb4efe53884b65d03a8af54697691c65171940d7b”
pubkey2 as “038394f560b8c911d53af81f6358661ea703ce943c53f62ff6bf021c51c1173628"
pubkey3 as “021b65552b3dd56eb663536b740527fc01c4e35bf7453c2fe794ed6eb4b2d9a3c7”
Get Abubakar Sadiq Ismail’s stories in your inbox
Join Medium for free to get updates from this writer.
The sciptPubkey of the above Pay to Multi Signature output is
0 1 0319c337fa76df71ecb4744c8bb4efe53884b65d03a8af54697691c65171940d7b 038394f560b8c911d53af81f6358661ea703ce943c53f62ff6bf021c51c1173628 021b65552b3dd56eb663536b740527fc01c4e35bf7453c2fe794ed6eb4b2d9a3c7 3 OP_CHECKMULTISIGOP_CHECKMULTISIG has a hex value of “ae”.
const op0 = "0"
const opCheckMultiSig = "ae"
const pubkey1 = "0319c337fa76df71ecb4744c8bb4efe53884b65d03a8af54697691c65171940d7b"
const pubkey2 = "038394f560b8c911d53af81f6358661ea703ce943c53f62ff6bf021c51c1173628"
const pubkey3 = "021b65552b3dd56eb663536b740527fc01c4e35bf7453c2fe794ed6eb4b2d9a3c7"
const m = "3"
const n = "1"
const scriptPubKeyP2MS = op0 + n + pubkey1 + pubkey2 + pubkey3 + m + opCheckMultiSig
console.log(scriptPubkeyP2MS)
//010319c337fa76df71ecb4744c8bb4efe53884b65d03a8af54697691c65171940d7b0383//94f560b8c911d53af81f6358661ea703ce943c53f62ff6bf021c51c1173628021b65552b//3dd56eb663536b740527fc01c4e35bf7453c2fe794ed6eb4b2d9a3c73ae4. P2SH (Pay to Script Hash)
For Pay to Script Hash, you will need a script hash.
A bitcoin script is the combination of public keys, or public key hashes and bitcoin opcodes, like Multisignature, or a time locked Opcodes. The constructed script is double hash first with sha256 and then RIPEMD160 which results in a script hash.
P2SH are in the format;
OP_HASH160 <script> OP_EQUALFor example, with the script hash “748284390f9e263a4b766a75d0633c50426eb875" we can construct a P2SH
OP_HASH160 <bytes to push> 748284390f9e263a4b766a75d0633c50426eb875 OP_EQUALThe hex value of OP_EQUAL is “87”;
const opEqual = "87"
const script = "748284390f9e263a4b766a75d0633c50426eb875"
const scriptPubKeyP2SH = opHash160 + script + opEqual
console.log(scriptPubKeyP2SH)
//a9748284390f9e263a4b766a75d0633c50426eb875874. P2WPKH (Pay to Witness Public Key Hash) and P2WSH (Pay to Witness Script Hash)
P2WPKH and P2WSH are almost similar, the major difference between the two scriptPubkeys is in the witness program length, P2WPKH witness program is 20 bytes while P2WSH is 32 bytes.
In P2WPKH public key hashes are used as the witness program which is 20 bytes whereas in P2WSH a script hash is used as the witness program which is sha256 hash of script.
P2WKH format;
scriptVersion <public key hash>The public key hash can be referred to as the witness program
P2WSH format;
scriptVersion <script hash>The script hash here also can be referred to as the witness program
Currently, there are two types of script versions 0 and 1.
An example of constructing a P2WPK with the 20 bytes witness program “ab68025513c3dbd2f7b92a94e0581f5d50f654e7” with version 0 witness program is
0 ab68025513c3dbd2f7b92a94e0581f5d50f654e7Whereas the witness program 32 bytes for P2WSH a9b7b38d972cabc7961dbfbcb841ad4508d133c47ba87457b4a0e8aae86dbb89 is using version 0 witness program
0 a9b7b38d972cabc7961dbfbcb841ad4508d133c47ba87457b4a0e8aae86dbb89const scriptP2wpkh = "ab68025513c3dbd2f7b92a94e0581f5d50f654e7"
const scriptP2wsh = "a9b7b38d972cabc7961dbfbcb841ad4508d133c47ba87457b4a0e8aae86dbb89"
const scriptPubKeyP2WPKH = op0 + scriptP2wpkh;
console.log(scriptPubKeyP2WPKH)
//0ab68025513c3dbd2f7b92a94e0581f5d50f654e7
const scriptPubKeyP2WSH = op0 + scriptP2wsh;
console.log(scriptPubKeyP2WSH)
//0a9b7b38d972cabc7961dbfbcb841ad4508d133c47ba87457b4a0e8aae86dbb89Bitcoin address to scriptPubkey
After decoding a bitcoin address you get will get a public key, public Key hash or a script hash depending on the data encoded in the address, Then we construct a scriptPubkey for the purpose of querying the bitcoin indexers to get an address balance or transactions or creating a new bitcoin transaction output.
The base58check ( https://www.npmjs.com/package/base58check ) Javascript library is used to decode a legacy bitcoin address and get the public key hash or script.
bech32 https://www.npmjs.com/package/bech32 Typescript library decodes a segwit address to generate the witness program encoded in the address.
The data decoded from a bitcoin address is used to construct a scriptPubKey,
Also, a public key can be converted to a public key hash.
The public keys for example Alice, Bob and Eva can be combined to create a joined account, an M of N threshold scriptPubKey (Pay to Multisig P2MS),
Scripts that define more sophisticated spending paths, conditional spending, and a time lock can also be used to construct a P2SH or P2WSH scriptPubKeys.
Read about the bitcoin script https://sha378.hashnode.dev/programmable-money
Let’s try out an example by using the base58check to decode a legacy bitcoin address and construct a P2PKH.
Step 1.
Install base58check library from node package manager repository.
npm i base58checkconst base58check = require('base58check')
const address = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
const { data } = base58check.decode(address,"hex")
console.log(data)
// 62e907b15cbf27d5425399ebf6f0fb50ebb88f18
const addrSriptPubKey = opDup +opHash160 + bytesToPush + data+ opEqualVerify + opCheckSig;
console.log(addrSriptPubKey)
//76a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1888acConclusion
We have seen how we can combine bitcoin script opcodes with public keys, or public key hash, script, or script hash to construct scriptPubkey.
The scriptPubkeys value must be added to a bitcoin transaction outputs,
It is an important part of transaction outputs, the opcodes in the scriptPubkey basically specify how a scriptPubKey can be unlocked or spent.
In the bitcoin blockchain ledger, there are no addresses only scriptPubkeys, to get address transactions or balance from the bitcoin blockchain ledger an address scriptPubkeys is used to query an indexer like electrumX (https://github.com/romanz/electrs), or when building a bitcoin wallet software you need to convert addresses to scriptPubKeys during creation of a new transaction output.







