Fadi Barbàra's Brand

Journey of a P2SH transaction - Part 2

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.

Alice installs the library

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.

Check the library example

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:

  1. An unspent transaction output (UTXO) she can spend from
  2. The related public key to prove she can spend that funds

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:

  1. Sign the transaction using the secret key sk Alice put in the script before
  2. Create the script to redeem the coins from the transaction in input txin
  3. Broadcast this transactions

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

Conclusions

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.

Appendix - How to find info

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 0th 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.