Intro
For several weeks I tried to play around bitcoin script. Yes, this may be news to you but bitcoin have a scripting language that acts as a very simple smart contract. This is an interesting topic because it reveals the internals of Bitcoin. What you should know is that bitcoins script is a scripting system for transactions. Forth-like, Script is simple, stack-based, and processed from left to right. It is intentionally not Turing-complete, with no loops.
A script is essentially a list of instructions recorded with each transaction that describes how the next person wanting to spend the Bitcoins being transferred can gain access to them. Scripting provides the flexibility to change the parameters of what’s needed to spend transferred Bitcoins. For example, the scripting system could be used to require two private keys, a combination of several keys, or even no keys at all.
To get the attention I will say that I found an address that regularly receives incoming transactions and from 2013 it already has a total amount of 45 bitcoins passed through this address. Just imagine at this moment this is around USD 800.000. Wow, and what if I will say that - you do not need the private key to access incoming transactions for this address? interesting? Then follow the article and you will get an idea of how it is possible.
Transaction
Now let’s review typical transaction:
{
"status": "success",
"data": {
"network": "BTC",
"txid": "65bbde44b8f7aa81835414107bc2c82ccdc7b5899c814890ad3325271ed52c38",
"blockhash": "00000000000000000000b8ed9b9f8de21838afc9d33ee061482c8498abc98165",
"block_no": 766898,
"confirmations": 1,
"time": 1670744234,
"size": 379,
"vsize": 189,
"version": 1,
"locktime": 0,
"sent_value": "0.03519711",
"fee": "0.00040000",
"inputs": [
{
"input_no": 0,
"address": "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej",
"value": "0.03519711",
"received_from": {
"txid": "7ff5dc8d104258b43b2d3ec53ce951466090e88a9bca0804b2f4415195551f54",
"output_no": 1
},
"script_asm": "",
"script_hex": "",
"witness": [
"",
"304402206ff3176eef356d4c6662b90f5b5cb0fd1c19f9c752270f9e1d595c5b203b9be502203cebbdc72bc7ae25216a50a822d45a03ffd3c22be25d49a7e29a93d9ef0c9b4201",
"3044022033ab84e7fa5ca75f9beb67206fcb71dd2a0f8a1af81cf7972a3f963d8da62e0802202bca905a34e5ec22ddb82e535503e3a4bbfd3fb5062f6aaefbcafaee0f22a2d101",
"52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae"
]
}
],
"outputs": [
{
"output_no": 0,
"address": "bc1qhg0ut27rgp2p3l0ns42s8mm66zlduerj0uy0pe",
"value": "0.00800000",
"type": "witness_v0_keyhash",
"req_sigs": null,
"spent": {
"txid": "408b43db96994a252f12ed72318c26e688f06779d1555d2a762614d852aa48df",
"input_no": 0
},
"script_asm": "0 ba1fc5abc3405418fdf3855503ef7ad0bede6472",
"script_hex": "0014ba1fc5abc3405418fdf3855503ef7ad0bede6472"
},
{
"output_no": 1,
"address": "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej",
"value": "0.02679711",
"type": "witness_v0_scripthash",
"req_sigs": null,
"spent": null,
"script_asm": "0 701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d",
"script_hex": "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d"
}
],
"tx_hex": "01000000000101541f55955141f4b20408ca9b8ae890604651e93cc53e2d3bb45842108ddcf57f0100000000ffffffff0200350c0000000000160014ba1fc5abc3405418fdf3855503ef7ad0bede64729fe3280000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d040047304402206ff3176eef356d4c6662b90f5b5cb0fd1c19f9c752270f9e1d595c5b203b9be502203cebbdc72bc7ae25216a50a822d45a03ffd3c22be25d49a7e29a93d9ef0c9b4201473044022033ab84e7fa5ca75f9beb67206fcb71dd2a0f8a1af81cf7972a3f963d8da62e0802202bca905a34e5ec22ddb82e535503e3a4bbfd3fb5062f6aaefbcafaee0f22a2d1016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000"
},
"code": 200,
"message": ""
}
Much of the information is self-descriptive. Let’s review some important elements of the transaction
Input
An input is a reference to an output from a previous transaction. Multiple inputs are often listed in a transaction. All of the new transaction’s input values (that is, the total coin value of the previous outputs referenced by the new transaction’s inputs) are added up, and the total (less any transaction fee) is completely used by the outputs of the new transaction. The previous tx is a hash of a previous transaction. The index is the specific output in the referenced transaction. ScriptSig is the first half of a script (discussed in more detail later).
The script contains two components, a signature, and a public key. The public key must match the hash given in the script of the redeemed output. The public key is used to verify the redeemer’s signature, which is the second component. More precisely, the second component is an ECDSA signature over a hash of a simplified version of the transaction. It, combined with the public key, proves the transaction was created by the real owner of the bitcoins in question. Various flags define how the transaction is simplified and can be used to create different types of payment.
{
"input_no": 0,
"address": "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej",
"value": "0.03519711",
"received_from": {
"txid": "7ff5dc8d104258b43b2d3ec53ce951466090e88a9bca0804b2f4415195551f54",
"output_no": 1
},
"script_asm": "",
"script_hex": "",
"witness": [
"",
"304402206ff3176eef356d4c6662b90f5b5cb0fd1c19f9c752270f9e1d595c5b203b9be502203cebbdc72bc7ae25216a50a822d45a03ffd3c22be25d49a7e29a93d9ef0c9b4201",
"3044022033ab84e7fa5ca75f9beb67206fcb71dd2a0f8a1af81cf7972a3f963d8da62e0802202bca905a34e5ec22ddb82e535503e3a4bbfd3fb5062f6aaefbcafaee0f22a2d101",
"52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae"
]
}
input_no - the increment number of input transactions. It starts from 0 and can be as many as you reference UTXO transactions to take money from.
txid - Transaction hash that you take UTXO from
script_asm - The asm strands for assembly, which is the symbolic representation of Bitcoin’s Script language op-codes.
script_hex - hex is just the serialized form of the script in hex encoding
witness - witness basically means a signature but in Bitcoin its much broader. We will not cover the witness topic in this article as it requires much more attention than a couple of words. So to get the money you do not need this right now.
Segregated Witness (SegWit) refers to a change in Bitcoin’s transaction format where the witness information was removed from the input field of the block. The stated purpose of Segregated Witness is to prevent non-intentional Bitcoin transaction malleability and allow for more transactions to be stored within a block. SegWit was also intended to solve a blockchain size limitation problem that reduced Bitcoin transaction speed.
Output
An output contains instructions for sending bitcoins. Value is the number of Satoshi (1 BTC = 100,000,000 Satoshi) that this output will be worth when claimed. ScriptPubKey is the second half of a script (discussed later). There can be more than one output, and they share the combined value of the inputs. Because each output from one transaction can only ever be referenced once by an input of a subsequent transaction, the entire combined input value needs to be sent in an output if you don’t want to lose it. If the input is worth 50 BTC but you only want to send 25 BTC, Bitcoin will create two outputs worth 25 BTC: one to the destination, and one back to you (known as “change”, though you send it to yourself). Any input bitcoins not redeemed in an output is considered a transaction fee; whoever generates the block can claim it by inserting it into the coinbase transaction of that block.
Segregated Witness (SegWit) refers to a change in Bitcoin’s transaction format where the witness information was removed from the input field of the block. The stated purpose of Segregated Witness is to prevent non-intentional Bitcoin transaction malleability and allow for more transactions to be stored within a block. SegWit was also intended to solve a blockchain size limitation problem that reduced Bitcoin transaction speed.
{
"output_no": 1,
"address": "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej",
"value": "0.02679711",
"type": "witness_v0_scripthash",
"req_sigs": null,
"spent": null,
"script_asm": "0 701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d",
"script_hex": "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d"
}
output_no - the increment number of output transactions. It starts from 0 and can be as many as you to which addresses (scripts) you will send your coins.
script_asm - The asm strands for assembly, which is the symbolic representation of Bitcoin’s Script language op-codes.
script_hex - hex is just the serialized form of the script in hex encoding
Scripts
So if you will decode the script_asm OR script_hex into human-readable format, you can see a set of opcodes. There is a RPC command in the bitcoin node to decode the script https://developer.bitcoin.org/reference/rpc/decodescript.html
Lets use this script_hex “a91438ab23d03d4abc12f36847970b1656aa6198572587”
> bitcoin-cli decodescript "hexstring"
Result:
{ (json object)
"asm" : "str", (string) Script public key
"desc" : "str", (string) Inferred descriptor for the script
"type" : "str", (string) The output type (e.g. nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_scripthash, witness_v0_keyhash, witness_v1_taproot, witness_unknown)
"address" : "str", (string, optional) The Bitcoin address (only if a well-defined address exists)
"p2sh" : "str", (string, optional) address of P2SH script wrapping this redeem script (not returned for types that should not be wrapped)
"segwit" : { (json object, optional) Result of a witness script public key wrapping this redeem script (not returned for types that should not be wrapped)
"asm" : "str", (string) String representation of the script public key
"hex" : "hex", (string) Hex string of the script public key
"type" : "str", (string) The type of the script public key (e.g. witness_v0_keyhash or witness_v0_scripthash)
"address" : "str", (string, optional) The Bitcoin address (only if a well-defined address exists)
"desc" : "str", (string) Inferred descriptor for the script
"p2sh-segwit" : "str" (string) address of the P2SH script wrapping this witness redeem script
}
}
Examples:
> bitcoin-cli decodescript "hexstring"
> curl --user myusername --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "decodescript", "params": ["hexstring"]}' -H 'content-type: text/plain;' http://127.0.0.1:8332/
$ bitcoin-cli decodescript a91438ab23d03d4abc12f36847970b1656aa6198572587
{
"asm": "OP_HASH160 38ab23d03d4abc12f36847970b1656aa61985725 OP_EQUAL",
"desc": "addr(2MxQrr2ygth64fByG6TWnWhvTGePBiU8fLr)#hgdutq4x",
"address": "2MxQrr2ygth64fByG6TWnWhvTGePBiU8fLr",
"type": "scripthash"
}
The field asm is what we are looking for !
OP_HASH160 38ab23d03d4abc12f36847970b1656aa61985725 OP_EQUAL
Opcodes
This is a list of all Script words, also known as opcodes, commands, or functions.
There are some words that existed in very early versions of Bitcoin but were removed out of concern that the client might have a bug in their implementation. This fear was motivated by a bug found in OP_LSHIFT that could crash any Bitcoin node if exploited and by other bugs that allowed anyone to spend anyone’s bitcoins. The removed opcodes are sometimes said to be “disabled”, but this is something of a misnomer because there is absolutely no way for anyone using Bitcoin to use these opcodes (they simply do not exist anymore in the protocol), and there are also no solid plans to ever re-enable all of these opcodes. They are listed here for historical interest only.
Pay-to-PubkeyHash
scriptPubKey: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
scriptSig: <sig> <pubKey>
Pay-to-Script-Hash
scriptPubKey: OP_HASH160 <scriptHash> OP_EQUAL
scriptSig: ..signatures... <serialized script>
The bitcoin address is derived from the public key through the use of one-way cryptographic hashing. Currently, Bitcoin addresses and their checksums are constructed from the public key by a using repeated hashing with SHA256 and RIPEMD160.
Address
So …. ScriptPubKey is a script to unlock the UTXO as well as at the same time use SHA256 and RIPEMD160 converted to a human-readable address that we are all using. Take a look at this website you can experiment with opcodes and convert your Script to P2SH Address for Mainnet and Testnet https://bitcoin-script-debugger.visvirial.com/
Where is the money Vadzim and why it is about puzzles ???
And now what if I will show you sample opcodes that do not require signatures but only has operations?
OP_0 OP_HASH160
take a look into script debugger and you will see this result b472a266d0…b16f7c3b9fcb
This means that you calculate hash160 from Zero. Very easy.
Now lets try to find Pay-to-Script-Hash with this Hash160.
OP_HASH160 472a266d0…b16f7c3b9fcb OP_EQUAL
And this will be address
3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy
Now take a look at this address on the blockchain explorer. https://www.blockchain.com/btc/address/3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy WOW 45 BTC was already transferred to this address. It’s funny but looks like dozen times per month someone sends coins to this address.
Now think deeper - you do not need PRIVATE keys to take these coins when they arrive at this address, the only thing you need to do is to provide input to Pay-to-Script-Hash. And we already know that this is Zero.
Please review an example of a recent transaction that was made.
{
"status": "success",
"data": {
"network": "BTC",
"txid": "b20f376a145fad210de40239d78bd58f0d876180df1fc721fcf292c5dd3b8eb0",
"blockhash": "00000000000000000002a9a7347207c48fa086f7a877dfa53ca8b2a7c4e36127",
"block_no": 766494,
"confirmations": 408,
"time": 1670519287,
"size": 84,
"vsize": 84,
"version": 1,
"locktime": 0,
"sent_value": "0.00005906",
"fee": "0.00000800",
"inputs": [
{
"input_no": 0,
"address": "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy",
"value": "0.00005906",
"received_from": {
"txid": "9198a78042c67747828f16354ca70842927500b0741d11722d19343e2ff4a034",
"output_no": 1
},
"script_asm": "1 0",
"script_hex": "5100",
"witness": null
}
],
"outputs": [
{
"output_no": 0,
"address": "bc1q8742wwqvxhfaxpahe23a9ggr3lflutq9fds4fr",
"value": "0.00005106",
"type": "witness_v0_keyhash",
"req_sigs": null,
"spent": null,
"script_asm": "0 3faaa7380c35d3d307b7caa3d2a1038fd3fe2c05",
"script_hex": "00143faaa7380c35d3d307b7caa3d2a1038fd3fe2c05"
}
],
"tx_hex": "010000000134a0f42f3e34192d72111d74b00075924208a74c35168f824777c64280a7989101000000025100ffffffff01f2130000000000001600143faaa7380c35d3d307b7caa3d2a1038fd3fe2c0500000000"
},
"code": 200,
"message": ""
}
Take a look closer
"script_asm": "1 0",
"script_hex": "5100",
So lets construct needed transaction in Rust programmin language
let script = Builder::new().push_int(1).push_int(0).into_script();
let mut raw_tx = Transaction {
version: 2,
lock_time: bitcoin::PackedLockTime(0),
input: vec![TxIn {
previous_output: OutPoint::from_str("dbeedda66415f0aed7a72e01e8ce553211e9fe8d0e017549204adb806e64b136:1").unwrap(),
script_sig: script,
sequence: bitcoin::Sequence(0xFFFFFFFF),
witness: witness,
}],
output: vec![TxOut {
value: 90000,
script_pubkey: myScript,
}],
};
println!("{:?}", serialize(&raw_tx).to_hex());
You will get HEX transaction which you can broadcast to the network
020000000136b1646e80db4a204975010e8dfee9113255cee8012ea7d7aef01564a6ddeedb01000000025100ffffffff01905f010000000000232103b50a9dbd32e7abe0fa689bed8a67fefd8e1b87953658b31c29ff24ba8de81e97ac00000000
So lets decode this to see what inside
> bitcoin-cli decoderawtransaction 02000000016d7c1615ee9b47e8deca0f5ef4a8cc0dff2e8bfac8e2f5352d9887ea038152ef01000000025100ffffffff01e069f9020000000023210299209a402b9f9bac266bc1aeb52eb862146cc220afe71b7bc7b729ca2530df11ac00000000
{
"txid": "226b00363c6bed138b1bcbc993f12431be23949e1c504628475672dc420d4191",
"hash": "226b00363c6bed138b1bcbc993f12431be23949e1c504628475672dc420d4191",
"version": 2,
"size": 97,
"vsize": 97,
"weight": 388,
"locktime": 0,
"vin": [
{
"txid": "ef528103ea87982d35f5e2c8fa8b2eff0dcca8f45e0fcadee8479bee15167c6d",
"vout": 1,
"scriptSig": {
"asm": "1 0",
"hex": "5100"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0.49900000,
"n": 0,
"scriptPubKey": {
"asm": "0299209a402b9f9bac266bc1aeb52eb862146cc220afe71b7bc7b729ca2530df11 OP_CHECKSIG",
"desc": "pk(0299209a402b9f9bac266bc1aeb52eb862146cc220afe71b7bc7b729ca2530df11)#u9pvpyvd",
"hex": "210299209a402b9f9bac266bc1aeb52eb862146cc220afe71b7bc7b729ca2530df11ac",
"type": "pubkey"
}
}
]
}
Remember you do not need a cryptographic signature because to provide the solution for locking script you just need to provide the Zero number.
Now the only thing you need to get your money is to send this raw transaction to the network.
bitcoin-cli sendrawtransaction 02000000016d7c1615ee9b47e8deca0f5ef4a8cc0dff2e8bfac8e2f5352d9887ea038152ef01000000025100ffffffff01e069f9020000000023210299209a402b9f9bac266bc1aeb52eb862146cc220afe71b7bc7b729ca2530df11ac00000000
This will work fine on Regtest network (developer installation of bitcoin network) but will still provide an error on Mainnet because it’s not a standard transaction. What you need to do to find Miner (or become a Miner) who will agree to include the transaction in a block. But hurry up - some one else can take these coins faster than you!
Conclusion
Experimenting with bitcoin scripts you can find many interesting addresses which sometimes even have coins you can take if you can solve these puzzles.