Last time we left the journey with Bob that sends the known part of the script to Alice. Now we shift our focus on Alice: she has to create a transaction using the known script.
Alice decides to use the Python library bitcoin-utils
for this job (link here) because it allows her to do things quickly and well. Alice realizes that the various abstractions of the library do not allow her to understand all the steps in detail: probably next time she will read the library code, and maybe in the future she will rewrite something in bash
to automate the process more. The ultimate goal is to write the API for bitcoin-core
, but let's take it one step at a time.
bitcoin-utils
needs Python 3, so to install it on her computer (she uses a version of Arch Linux, but that should work on any Linux distribution) she does
pip3 install bitcoin-utils
As easy as that.
This library isn't perfect, as you can not use it in production. But, as the project description says:
This is a bitcoin library that provides tools/utilities to interact with the Bitcoin network. One of the primary goals of the library is to explain the low-level details of Bitcoin. The code is easy to read and properly documented explaining in detail all the thorny aspects of the implementation. It is a low-level library which assumes some high-level understandingi of how Bitcoin works. In the future this might change. [...] It is not meant for production yet and parts of the API might be updated with new versions.
The project has also a lot of examples. The one we need is here. There is no need to paste here the code in its entirety as it would only bloat the page. I paste only the important parts. In the following, Alice just follows the example provided by the project. There is a lot of room for improvement: as we will see there is no need to manually provide the amount of a transaction, if we already have the transaction id and the position (i.e. the number) of the output we want to spend from.
The first part, before the main
invocation just imports the function. I assume you understand basic Python; if you do not, let's just say that if you want to use functions defined elsewhere, you have to tell your computer where to find them.
First important line is this:
setup('testnet')
You have to tell the script which network you are using. Those are complex things, so it is better to be safe than sorry. Alice and Bob are ok in doing a dry run on the testnet
network first. This way they also see if this library is working properly (remember that it is not ready for production yet).
Because Alice wants to send some funds to Bob, she needs two things:
Alice is practically a novice, so she just assumes she has to spend from a P2PKH transaction: that's why she needs exactly those two things. In theory, you could also spend from other types of transactions, e.g. with witness, or redeem another P2SH transaction: in those cases you would need different things. But she prefers to take one step at a time.
So she provides the information in those two lines of code. Here we depart from the example in the mentioned link of course: Alice has a different transaction and a different private key. Also, from now on you can follow the journey of the P2SH transaction too! I'll provide a link to a block explorer in the conclusions.
txin = TxInput('3c286773e75e16eab9b78c23a132f445b9eee64dd0f8b3049385f9de043d3609', 0)
sk = PrivateKey('cUR8enPaTgRgYW4dpB6VdjhHFpfAQKz3QuDqPiGbWMtHijhSTAVG')
Please, note that the TxInput
function has two inputs. The first one is the transaction id of the UTXO, while the second is the number of chosen output. At the end of this post we'll see how Alice can choose the output based on her transactions and get her private key. We'll assume she has her bitcoin-core
node, because Alice knows how crucial it is to have her own node (even if she does not mine, don't confuse those things!)
Now we diverge a little from the example, because Alice has a different script in mind. The script in the example has a CHECKSIG
op code, Alice's doesn't: their script is more secure than the one Bob gave Alice. Alice's known script is (here I provide the OP_
because it's needed):
OP_SHA256 eb2e10773d403f9972939ede70382fc05c43260b1beb0d5163f3b39dbc8f964b OP_EQUAL
So we add those lines to the Python script (note that we have two different scripts: one is the Bitcoin script in the Script language, the other is the Python script used to send the P2SH transaction: you should appreciate how mouthful the description of our situation is). The redeem_script
is what we called the known script
redeem_script = Script(["OP_SHA256", "eb2e10773d403f9972939ede70382fc05c43260b1beb0d5163f3b39dbc8f964b", "OP_EQUAL"])
After creating the script, Alice has to finish building the transaction. Right now she has both the transaction input and the Script script for the transaction output. She still needs to add the amount to that script. You will see that the amount is based on the input transaction reported in the appendix. The fees are calculated at random (and they are very high) and furthermore I assume that Alice has to pay Bob the entire content of the transaction. In reality things are a bit different: there has to be a function that calculates the fees and most of the time Alice won't have to use the entire output. All of these things will be seen when Alice feels more confident.
txout = TxOutput(to_satoshis(0.014), redeem_script.to_p2sh_script_pub_key() )
The line above just package the amount with a transformation of the redeem_script
in a script_pub_key
. That's because the Script language doesn't directly use the opcodes, but it needs a transformation of them, and because the Script script has to be hashed. But those technicalities will be explored in detail later.
As of now, then, we have all the info we need. Now we have to act on them. In particular we have to:
sk
Alice put in the script beforetxin
First point is easy. In the following two lines, Alice creates the whole transactions using both the txin
and the txout
, and then she signs it
tx = Transaction([txin], [txout])
from_addr = P2pkhAddress('mgD4equukFPx3qtFx3YMR3zz48zBY6WzL2')
sig = sk.sign_input(tx, 0, from_addr.to_script_pub_key())
There is an obvious inefficiency: the from_addr
shouldn't be provided because it is either in the txin
(see the appendix below) or it can be derived from the secret key sk
. As I said, things aren't perfect yet, Alice will deal with it in the future.
The second point has this simple line of codes
pk = sk.get_public_key().to_hex()
txin.script_sig = Script([sig, pk])
signed_tx = tx.serialize()
For the third point, just print the signed transaction signed_tx
and the transaction id:
print("\nRaw signed transaction:\n" + signed_tx)
print("\nTxId:", tx.get_txid())
You can find Alice's script here. Remember that Alice has her node. To broadcast the transaction she just does:
$ python send-p2sh-tx.py
Obtaining as output:
Raw signed transaction:
020000000109363d04def9859304b3f8d04de6eeb945f432a1238cb7b9ea165ee77367283c000000006a47304402200fdb2ad936fe69b314cfd739bc6b51f109fedfa12726b3f14ca904c39d9f32b0022001f6c008f70d1536be77a873d8b7273fa50d1fba7a1add7f19113d4da1254cfd012102b750e12d8b7175075984835bdb64f62749d8dcca5d8290b64b334888d76d9ec7ffffffff01c05c15000000000017a914b3b3ae4c1c7db109e72000a9fdd7275e1ba4cf328700000000
TxId:
c8cca62403882df10ba79ebdc7c8327e3a85faad0208ed818cc39343003236a6
Then she just broadcasts the "Raw signed transaction" above
$ bitcoin-cli sendrawtransaction 020000000109363d04def9859304b3f8d04de6eeb945f432a1238cb7b9ea165ee77367283c000000006a47304402200fdb2ad936fe69b314cfd739bc6b51f109fedfa12726b3f14ca904c39d9f32b0022001f6c008f70d1536be77a873d8b7273fa50d1fba7a1add7f19113d4da1254cfd012102b750e12d8b7175075984835bdb64f62749d8dcca5d8290b64b334888d76d9ec7ffffffff01c05c15000000000017a914b3b3ae4c1c7db109e72000a9fdd7275e1ba4cf328700000000c8cca62403882df10ba79ebdc7c8327e3a85faad0208ed818cc39343003236a6
The journey of a P2SH has reached a new stage: Alice has sent the transaction on the blockchain, you can see it here. The next step will be about Bob, who has to redeem it.
As we said earlier, Alice has her own bitcoin-core
node. First thing she has to do, is to know which transactions she has. We assume for the sake of simplicity she has one transaction with more than the whole amount. In fact, that's the case (first line of the shell output is an alias for the bitcoin-cli
program; always remember that I am lazy):
$ alias btc="bitcoin-cli -testnet"
$ btc listunspent
[
{
"txid": "3c286773e75e16eab9b78c23a132f445b9eee64dd0f8b3049385f9de043d3609",
"vout": 0,
"address": "mgD4equukFPx3qtFx3YMR3zz48zBY6WzL2",
"label": "fromap2sh",
"scriptPubKey": "76a9140795f23caf5073a2758f98369e18eecb8c3967f188ac",
"amount": 0.01450000,
"confirmations": 7802,
"spendable": true,
"solvable": true,
"desc": "pkh([d3ae48fd/0'/0'/13']02b750e12d8b7175075984835bdb64f62749d8dcca5d8290b64b334888d76d9ec7)#tcha5fxx",
"safe": true
},
{
"txid": "c91a11fe91693e514aaf3a5bab2e63b70406f43cb942b13891ff286f0cbd860a",
"vout": 0,
"address": "tb1qr36tvz0nvxz58ktv39srzt575kxknhhl2lezqf",
"scriptPubKey": "00141c74b609f3618543d96c8960312e9ea58d69deff",
"amount": 0.00088173,
"confirmations": 8061,
"spendable": true,
"solvable": true,
"desc": "wpkh([d3ae48fd/0'/1'/4']02859c0777d0b9c3a00ae5c719834e0ab54a6e8663cf125080b9eff3040a360970)#v2w4df5f",
"safe": true
},
{
"txid": "d45bd317e029c8aa62b5585f160b98b2a6af703d0e0e81d2e7c9e81c195a2e59",
"vout": 0,
"address": "2N4VjhbXVJ61xzH16BPRhcLTjh4vNjfkyfB",
"label": "legacy",
"redeemScript": "0014d7c66579f2426bb729cd3d552a50f0dc60d58263",
"scriptPubKey": "a9147b680278402098404c7d8a3c5f3f6edb4ceca97a87",
"amount": 0.00100000,
"confirmations": 74232,
"spendable": true,
"solvable": true,
"desc": "sh(wpkh([d3ae48fd/0'/0'/8']03bf5e0270daebf6c6fc62aec3411a13d8937ab0f320879234165ade708db509f4))#sxukgpa6",
"safe": true
}
]
You easily can finde three blocks, each one between {...}
. Each one of these blocks is one transaction Alice can spend. From the desc
field of the JSON you can see different kind of transactions: in order from the first one to the last one, there is the result of a P2PKH transaction, a P2WPKH transaction and a P2WPKH transaction.
Now we get all the info we need: remember that we want a P2PKH transaction, so we have to take the first one, i.e. the 0
th one. In addition to bitcoin-cli
, we also use:
txid=$(btc listunspent|jq .[0].txid|tr -d '"')
address=$(btc listunspent|jq .[0].address|tr -d '"')
amount=$(btc listunspent|jq .[0].amount|tr -d '"')
privkey=$(btc dumpprivkey $address)
We could easily selected the text with the mouse and then paste it, I know. But remember that Alice wants to create an utility, so the more she can script the better.