Skip to main content

Stupid Simple Locking Script With Python

· 6 min read
featured image

Ever wondered how to lock sats using a simple mathematical equation rather than a traditional signature? In this guide, we delve into the world of Bitcoin scripting, showcasing how you can lock sats using a straightforward equation and then unlock them to spend into another address. Of course we advise against doing this on mainnet so someone doesn't steal your precious sats. Of course, because we're not degens we'll do this on our own regtest network, a sandbox environment perfect for such explorations.

This is the equation we will use to lock our sats:

4+x=124 + x = 12

If you want to follow along this guide you must:

  • run Bitcoin regtest network with -txindex flag enabled
  • have basic understanding of bitcoin-cli
  • install python-bitcoinlib library for Python code

1. Create an address that locks our sats

Given our equation we know that in order to unlock the funds user must provide value xx. This means the locking script would look like this:

4 OP_ADD 12 OP_EQUAL

OP_ADD: pops the top two items of the stack and adds them OP_EQUAL: validates if 2 values are equal; returns 1 if they are equal, otherwise 0

If you're struggling to understand how we've come to the above script for the locking mechanism, I suggest you check out learnmeabitcoin.com/technical/script. It nicely shows how Bitcoin scripts are executed. We'll also show how the unlocking + locking script are executed on the stack.

Python code

import hashlib

from bitcoin import SelectParams
from bitcoin.core import b2x, lx, COIN, COutPoint, CTxOut, CTxIn, CMutableTransaction, CTxInWitness, CTxWitness
from bitcoin.core.script import CScript, CScriptWitness, OP_0, OP_ADD, OP_EQUAL, SIGVERSION_WITNESS_V0
from bitcoin.wallet import CBitcoinSecret, CBitcoinAddress, P2WSHBitcoinAddress, P2WPKHBitcoinAddress

# select regtest network
SelectParams('regtest')

# create a stupid simple locking script based on our equation 4 + x = 12
witness_script = CScript([4, OP_ADD, 12, OP_EQUAL])

# construct a segwit script pubkey
script_pubkey = CScript([OP_0, hashlib.sha256(witness_script).digest()])

address = P2WSHBitcoinAddress.from_scriptPubKey(script_pubkey)
print(f"Address locked by simple equation: {address}")
note

The above script will always generate the same address. To create a unique address for each transaction even when using the same locking script you can incorporate some form of unique data into the script, which in turn will change the script hash and consequently the address. This technique is often used in Bitcoin scripts to generate distinct addresses for transactions that fundamentally perform the same operation.

Send funds to our newly created address:

./bitcoin-cli sendtoaddress bcrt1qfpsegcdj8cpgjk3udgpmjg2y3yf4dg6w3e0z9ffkhp9fwx0fc2nsr06kjk 0.03 

Above code will output a transaction id which you have to save for later when we'll spend our locked sats.

2. Spend the sats by solving the equation

It's time we solve the stupid simple equation and get our sats back. Our locking script holds the equation and in our unlocking script we must provide the value to solve the equation. Solution so solve the equation is 8:

4+x=12x=124x=84 + x = 12 \\\\ x = 12 - 4 \\\\ x = 8

Python code

# we've deposited 0.03 bitcoins and we want to spend all of them
amount_locked = int(0.0300000 * COIN)
amount_minus_fee = int(amount_locked - (0.001 * COIN))

destination_address = P2WPKHBitcoinAddress('bcrt1qwjlun5qcz795fu0caxvdlc4xl9z6v5e0emvzzk')
target_scriptPubKey = destination_address.to_scriptPubKey()

# set txid and vout, this can be found when calling:
# ./bitcoin-cli gettransaction <saved transaction id>
txid = 'ad017e28863b8755d09b410538aa93b9cfae51ac00d24b05f2f39c1e8a5d052e'
vout = 1

txin = CTxIn(COutPoint(lx(txid), vout))
txout = CTxOut(amount_minus_fee, target_scriptPubKey)

# construct a transaction that solves the stupid simple equation, unlocking the funds and moving them to a new address
tx = CMutableTransaction([txin], [txout])

# b"\x08" represents a byte string containing a single byte whose value is 8
witness = [b'\x08', witness_script]
ctxinwitnesses = [CTxInWitness(CScriptWitness(witness))]

tx.wit = CTxWitness(ctxinwitnesses)

print("Serialized transaction, ready to be broadcasted: \n{}".format(b2x(tx.serialize())))
info

Note that in SegWit transactions, the witness_script used to lock the address is crucial for unlocking your funds later. This script is not publicly visible on the blockchain and is only revealed when you spend the funds. Therefore, it's essential to remember or securely store the exact witness_script you used initially, as you'll need to provide it to unlock and access your funds.

3. How Execution of This Script Works

A rough overview of how Bitcoin Script execution works. Fore a lot better article we recommend you have a look at learnmeabitcoin.com/technical/script article.

In order to unlock funds you must provide unlocking script and locking script.

In our example this is:

  • the locking script: 4 OP_ADD 12 OP_EQUAL
  • invalid unlocking script: 4
  • valid unlocking script: 8

Invalid Script Execution

Full script consists of unlocking (4) + locking script (4 OP_ADD 12 OP_EQUAL):

4 4 OP_ADD 12 OP_EQUAL
+----------+-------+
| Script | Stack |
+----------+-------+
| 4 | |
| 4 | |
| OP_ADD | |
| 12 | |
| OP_EQUAL | |
+----------+-------+

---

+----------+-------+
| Script | Stack |
+----------+-------+
| 4 | 04 | 4 gets added to the stack
| OP_ADD | |
| 12 | |
| OP_EQUAL | |
+----------+-------+

---

+----------+-------+
| Script | Stack |
+----------+-------+
| OP_ADD | 04 | another 4 gets added to the stack
| 12 | 04 |
| OP_EQUAL | |
+----------+-------+

---

+----------+-------+
| Script | Stack |
+----------+-------+
| 12 | 08 | OP_ADD pops the top two items of the stack and adds them, resulting in 8
| OP_EQUAL | |
+----------+-------+

---

+----------+-------+
| Script | Stack |
+----------+-------+
| OP_EQUAL | 12 | 12 gets added to the stack
| | 08 |
+----------+-------+

---

+----------+-------+
| Script | Stack |
+----------+-------+
| | 0 | OP_EQUAL validates two items, because they are invalid it returns a 0
+----------+-------+

Script is invalid because in the end 0 remains in the stack.

Valid Script Execution

Full script consists of unlocking (8) + locking script (4 OP_ADD 12 OP_EQUAL):

8 4 OP_ADD 12 OP_EQUAL
+----------+-------+
| Script | Stack |
+----------+-------+
| 8 | |
| 4 | |
| OP_ADD | |
| 12 | |
| OP_EQUAL | |
+----------+-------+

---

+----------+-------+
| Script | Stack |
+----------+-------+
| 4 | 08 | 8 gets added to the stack
| OP_ADD | |
| 12 | |
| OP_EQUAL | |
+----------+-------+

---

+----------+-------+
| Script | Stack |
+----------+-------+
| OP_ADD | 04 | 4 gets added to the stack
| 12 | 08 |
| OP_EQUAL | |
+----------+-------+

---

+----------+-------+
| Script | Stack |
+----------+-------+
| 12 | 12 | OP_ADD pops the top two items of the stack and adds them, resulting in 12
| OP_EQUAL | |
+----------+-------+

---

+----------+-------+
| Script | Stack |
+----------+-------+
| OP_EQUAL | 12 | 12 gets aded to the stack
| | 12 |
+----------+-------+

---

+----------+-------+
| Script | Stack |
+----------+-------+
| | 1 | OP_EQUAL validates two items, because they are valid it returns a 1
+----------+-------+

Script is valid because in the end 1 remains in the stack.