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:
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 . 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}")
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
:
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())))
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.