profile picture

Programmable Money

The power of Bitcoin's Script

July 06, 2024 - 1843 words - 10 mins Found a typo? Edit me
software bitcoin

blog-cover

Bitcoin is often referred to as “programmable money” because it allows for the execution of programmable transactions through its scripting language.

Understanding Programmable Money

Programmable money is the ability to embed logic and conditions into financial transactions. This programmability allows transactions to be executed automatically based on predefined rules without intermediaries or manual intervention. It transforms money from a static medium of exchange into a dynamic tool capable of executing complex agreements and automating financial operations.

The Script Language

Bitcoin’s programmability is powered by its built-in scripting language, Script. Unlike traditional programming languages, Script is a stack-based, Forth-like language explicitly designed for Bitcoin transactions.

It supports multi-signature, time-locked, and other conditional transfers that can be programmed into Bitcoin transactions. It is intentionally not Turing-complete, without loops.

This video includes examples of the most commonly used locking/unlocking scripts.

Key Features of Bitcoin’s Script

Stack-Based Execution

Script operates on a stack-based execution model where commands and data are pushed onto a stack and processed in a Last-In-First-Out (LIFO) manner.

Conditional Spending

A transaction that can only be spent if certain data is provided or specific criteria are met. This can be used for:

Multisignature

Transactions can be set up to require multiple signatures from different private keys before they can be spent. Useful for:

Timelocking

Transactions can include time-based conditions that prevent them from being spent until a certain time or block height is reached. This feature is useful for various purposes:

Operation Codes

Bitcoin’s Script performs specific operations within transactions with its OP_Codes. Here are some of them:


Common Bitcoin address types

In Bitcoin, different address types correspond to various ways to script transactions. Here, we’ll explore examples of Bitcoin Script for each major address type. Each address type has its own specific script format.

Using native SegWit (P2WPKH and P2WSH) is preferable when possible, as it maximizes the benefits of the SegWit upgrade, but P2SH-SegWit can be useful for compatibility with older systems.

P2PK (Pay-to-PubKey) - Earliest Legacy Address up

Begins with "1" (e.g., 1A1zP1eP5QGefi2DMPTf...v7DivfNa)

Before P2PKH and P2SH became standard, Bitcoin addresses were not as flexible or feature-rich. Here are some considerations:


P2PKH (Pay-to-PubKey-Hash) - Legacy Address up

Begins with "1" (e.g., 1A1zP1eP5QGefi2DMPTf...v7DivfNa)

A typical P2PKH script consists of two main parts:

  1. ScriptPubKey: The locking script (also known as the output script) that specifies how funds can be spent.
  2. ScriptSig: The unlocking script (also known as the input script) that provides the necessary data to unlock the funds.
ScriptPubKey (Locking Script)
OP_DUP OP_HASH160 <PubKHash> OP_EQUALVERIFY OP_CHECKSIG
ScriptSig (Unlocking Script)
<sig> <PubK>

Execution Flow

  1. The ScriptSig (unlocking script) is pushed onto the stack.
  2. The ScriptPubKey (locking script) is executed.

cover cover


P2SH (Pay-to-Script-Hash) up

Begins with "3" (e.g., 3J2BtwzN2GEr6FCP.....81T2eiX8PVHh)

P2SH scripts are used for more complex scripts. The primary feature is that the address itself encodes a hash of a script, which will be used in the transaction.

ScriptPubKey (Locking Script)
OP_HASH160 <ScriptHash> OP_EQUAL
ScriptSig (Unlocking Script)
<sig> <PubK> ... <ScriptSig>

P2MS (Pay-to-Multisig) up

Script Format
OP_M <M> <PubK1> <PubK2> ... <PubKN> OP_N OP_CHECKMULTISIG
ScriptPubKey (Locking Script)
OP_2 <PubK1> <PubK2> <PubK3> OP_3 OP_CHECKMULTISIG

This script means that any 2 out of 3 provided public keys are required to sign the transaction for it to be valid.

ScriptSig (Unlocking Script)
<sig1> <sig2> ... <sigN> <SerializedScript>
NOTE: There is an oddity in CHECKMULTISIG execution. See note at the bottom.

P2WPKH (Pay-to-Witness-Public-Key-Hash) - Segwit up

Begins with "bc1q" (e.g., bc1qf0r2m0ck4psv6yrk9w.....kw8v5rj7ph3)

P2WPKH is a Segregated Witness (SegWit) address type that uses a different scripting format compared to legacy and P2SH addresses. Simplifies transactions by reducing data size and fees compared to legacy formats.

ScriptPubKey (Locking Script)
OP_0 OP_PUSHBYTES_20 <PubKHash>
Witness Data

For P2WPKH, the unlocking script is not required in the traditional sense (i.e., inside the unlocking script explicitly included in the transaction input). Instead, the unlocking information is provided as part of the witness data in the SegWit transaction format.

<sig> <PubK>

P2WSH (Pay-to-Witness-Script-Hash) - Segwit up

Begins with bc1q (e.g.: bc1q4a3h5sdg4cfkhftgd24tj9g2sg...yj57jmfckhkrw5gslr9g59)
ScriptPubKey (Locking Script)
OP_0 OP_PUSHBYTES_32 <ScriptHash>
Witness Data
<sig1> <sig2> ... <RedeemScript>

P2TR (Pay-to-Taproot) - Taproot up

Begins with bc1p (e.g.: bc1pl9dfv7kvj4hj9s3a8l.....gjstmrpjl09g8ks3ukds70q4r2j5h)

Taproot combines Schnorr signatures with MAST, enabling private, efficient spending conditions and making complex transactions appear standard unless conditions are revealed. It allows the efficient execution of complex transactions while hiding their details.

ScriptPubKey (Locking Script)
OP_1 <x-only PubK>
Witness Data
<sig>


*There is an oddity in CHECKMULTISIG execution up

The implementation of OP_CHECKMULTISIG pops one more item than it should. The extra item is disregarded when checking the signatures, so it has no direct effect on the OP itself. It must be present because if OP_CHECKMULTISIG attempts to pop on an empty stack, it will cause a stack error and script failure.

OP_0 <sig2> <sig3> 2 <PubK1> <PubK2> <PubK3> 3 OP_CHECKMULTISIG

The input script in this multisig is not <sig2> <sig3> but OP_0 <sig2> <sig3>.

It because the custom early on to use OP_0 which later because a relay policy rule and eventually a consensus rule (BIP147).

It is possible that the original developer added the extra element in the original version of Bitcoin, so they could add a feature for allowing a map to be passed in a later soft fork (for performance reasons). However, that feature was never implemented, and the BIP147 update to the consensus rules in 2017 makes it impossible to add that feature in the future.

Only Bitcoin’s original developer could tell whether the dummy stack element was the result of a bug or a plan for a future upgrade. From now on, if you see a multisig script, you should expect to see an extra OP_O in the beginning, whose only purpose is as a workaround to an oddity in the consensus rules.


Follow-ups