8.3: Using a Partially Signed Bitcoin Transaction
Now that you've learned the basic workflow of generating a PSBT, you probably want to do something with it. What can PSBTs do that multisigs (and normal raw transactions) can't? To start with, you've got the ease of use of a standardized format, which means that you can use your bitcoin-cli transactions and meld them with transactions generated by people (or programs) on other platforms. Beyond that, you can do some things that just weren't easy using other mechanics.
Following are two more use cases for PSBTs, to add on to the multisigs of §8.1: pooling money and joining coins. We'll then add on a fourth use case in §8.5, using hardware devices.
Use a PSBT to Pool Money
Multisigs like the one used in §8.1 are often used to receive payments for collaborative work, whether it be royalties for a book or payments made to a company. In that situation, two participants receive money which they might later split up. But what about the converse case, where two (or more) participants want to set up a joint venture, and they need to seed it with money?
The traditional answer is to create a multisig, then to have the participants individually send their funds to it. The problem is that the first payer has to depend on the good faith of the second, and that doesn't build on the strength of Bitcoin, which is its trustlessness. Fortunately, with the advent of PSBTs, we can now make trustless payments that pool funds.
:book: What does trustless mean? Trustless means that no participant has to trust any other participant. They instead expect the software protocols to ensure that everything is enacted fairly in an expected manner. Bitcoin is a trustless protocol because you don't need anyone else to act in good faith; the system manages it. Similarly, PSBTs allow for the trustless creation of transactions that pool or split funds.
The following example shows two users who each have 0.001 BTC that they want to pool to the multisig address tb1qpavmrq2l7n5hvx4pgtay8n8ny7kg6k6xezkk2ha0ukx7swrrpclsadt9k0.
machine1$ bitcoin-cli listunspent
[
{
"txid": "f0f12e562ff6c49755e5e8d2177182d0fec3cfff86acb6c28fa3cf22dc6e90a3",
"vout": 0,
"address": "tb1q5pxrt2lkq4almf5xthu4kw75hexmgsnrwtndm8",
"label": "",
"scriptPubKey": "0014a04c35abf6057bfda6865df95b3bd4be4db44263",
"amount": 0.00100000,
"confirmations": 1,
"spendable": true,
"solvable": true,
"desc": "wpkh([d2743951/84h/1h/0h/0/13]03fcf36ab48a44819bd744c0970b25730d4acf17e37e14ca380d2ac50479fe6383)#u0acp2af",
"parent_descs": [
"wpkh([d2743951/84h/1h/0h]tpubDCffoASgcwmf3Fbyma7nUGWYH7Dk6DB8mnBBBEXR68UAHRC1AXn5yQaoR1CWJTJ5FLw7m5oNidSvbcbfAsemBkFmmkw3gtWKqArdMFu2wPq/0/*)#l54g93tl"
],
"safe": true
}
]
machine2$ bitcoin-cli listunspent
[
{
"txid": "9db5e03f2a61c41a34f67025e233b6ab6ba2fcf126e5f64a12df5770be60cadd",
"vout": 1,
"address": "tb1qsavzv7mld4rmc4mfl36g6zsryf97efggjvary7",
"label": "",
"scriptPubKey": "00148758267b7f6d47bc5769fc748d0a03224beca508",
"amount": 0.00100000,
"confirmations": 1,
"spendable": true,
"solvable": true,
"desc": "wpkh([e18dae20/84h/1h/0h/0/9]02dc1803a9c0636de6eca1891ce4500ec1e5a66d6752494eb16c6e6ee3faa9f90a)#7kn6haeq",
"parent_descs": [
"wpkh([e18dae20/84h/1h/0h]tpubDC4ujMbsd9REzpGk3gnTjkrfJFw1NnvCpx6QBbLj3CHBzcLmVzssTVP8meRAM1WW4pZnK6SCCPGyzi9eMfzSXoeFMNprqtgxG71VRXTmetu/0/*)#3658f8sn"
],
"safe": true
}
]
📖 Is it a little handy that the users both have .001 ready to send? Yes! A more realistic transaction would have each user spend a UTXO, send an appropriate amount of change back to themselves, and send the rest on to the multisig. Our example where they each have the right funds ready to go considerably simplifies that, but doesn't change the basic precept, which is that both users will examine the joint PSBT, make sure it says what it should, and only then sign it. (But a user could also do exactly what we did, which is send the .001 on to themselves in preparation for sending it into the multisig.)
They set up variables to use those transactions:
$ utxo_txid_1=f0f12e562ff6c49755e5e8d2177182d0fec3cfff86acb6c28fa3cf22dc6e90a3
$ utxo_vout_1=0
$ utxo_txid_2=9db5e03f2a61c41a34f67025e233b6ab6ba2fcf126e5f64a12df5770be60cadd
$ utxo_vout_2=1
$ multisig=tb1qpavmrq2l7n5hvx4pgtay8n8ny7kg6k6xezkk2ha0ukx7swrrpclsadt9k0
And create a PSBT:
$ psbt=$(bitcoin-cli -named createpsbt inputs='''[ { "txid": "'$utxo_txid_1'", "vout": '$utxo_vout_1' }, { "txid": "'$utxo_txid_2'", "vout": '$utxo_vout_2' } ]''' outputs='''{ "'$multisig'": 0.0019998 }''')
Here's what it looks like:
$ bitcoin-cli decodepsbt $psbt
{
"tx": {
"txid": "8bb0a0227b08596879502054bbdd0ace4cf8c686e32175aae2addbd9a1276337",
"hash": "8bb0a0227b08596879502054bbdd0ace4cf8c686e32175aae2addbd9a1276337",
"version": 2,
"size": 135,
"vsize": 135,
"weight": 540,
"locktime": 0,
"vin": [
{
"txid": "f0f12e562ff6c49755e5e8d2177182d0fec3cfff86acb6c28fa3cf22dc6e90a3",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967293
},
{
"txid": "9db5e03f2a61c41a34f67025e233b6ab6ba2fcf126e5f64a12df5770be60cadd",
"vout": 1,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967293
}
],
"vout": [
{
"value": 0.00199980,
"n": 0,
"scriptPubKey": {
"asm": "0 0f59b1815ff4e9761aa142fa43ccf327ac8d5b46c8ad655fafe58de838630e3f",
"desc": "addr(tb1qpavmrq2l7n5hvx4pgtay8n8ny7kg6k6xezkk2ha0ukx7swrrpclsadt9k0)#9fd8djj0",
"hex": "00200f59b1815ff4e9761aa142fa43ccf327ac8d5b46c8ad655fafe58de838630e3f",
"address": "tb1qpavmrq2l7n5hvx4pgtay8n8ny7kg6k6xezkk2ha0ukx7swrrpclsadt9k0",
"type": "witness_v0_scripthash"
}
}
]
},
"global_xpubs": [
],
"psbt_version": 0,
"proprietary": [
],
"unknown": {
},
"inputs": [
{
},
{
}
],
"outputs": [
{
}
]
}
It doesn't matter that the UTXOs are owned by two different people or that their full information appears on two different machines. This funding PSBT will work exactly the same as the multisig PSBT: once all of the controlling parties have signed, then the transaction can be finalized.
Here's the process, this time passing the partially signed PSBT from one user to another rather than having to combine things at the end.
machine1$ bitcoin-cli walletprocesspsbt $psbt
{
"psbt": "cHNidP8BAIcCAAAAAqOQbtwiz6OPwrashv/Pw/7QgnEX0ujlVZfE9i9WLvHwAAAAAAD9////3cpgvnBX3xJK9uUm8fyia6u2M+IlcPY0GsRhKj/gtZ0BAAAAAP3///8BLA0DAAAAAAAiACAPWbGBX/TpdhqhQvpDzPMnrI1bRsitZV+v5Y3oOGMOPwAAAAAAAQCaAgAAAAIbhNVXKnjnV/AqbpLsHcTX2o6UBHvnEAWpAjyQmJYZMgAAAAAA/f///94K5mHzCaeDCPihBY+H5HvnLx2Esc+ADVHVcy8YZ4SlDgIAAAD9////AqCGAQAAAAAAFgAUoEw1q/YFe/2mhl35WzvUvk20QmNaMQMAAAAAABYAFEOKAfmCxUw9x1ml9mRxf+4vX07jAAAAAAEBH6CGAQAAAAAAFgAUoEw1q/YFe/2mhl35WzvUvk20QmMBCGsCRzBEAiATgsjBS2psH79D+R/inzDf2Wl7aYgys0Q75bW622ZzKQIgHn4CpXHxw8/HnhORMLaOI7KzSmiY3pb809NjONmyshUBIQP882q0ikSBm9dEwJcLJXMNSs8X434UyjgNKsUEef5jgwAAAA==",
"complete": false
}
machine2$ psbt_p="cHNidP8BAIcCAAAAAqOQbtwiz6OPwrashv/Pw/7QgnEX0ujlVZfE9i9WLvHwAAAAAAD9////3cpgvnBX3xJK9uUm8fyia6u2M+IlcPY0GsRhKj/gtZ0BAAAAAP3///8BLA0DAAAAAAAiACAPWbGBX/TpdhqhQvpDzPMnrI1bRsitZV+v5Y3oOGMOPwAAAAAAAQCaAgAAAAIbhNVXKnjnV/AqbpLsHcTX2o6UBHvnEAWpAjyQmJYZMgAAAAAA/f///94K5mHzCaeDCPihBY+H5HvnLx2Esc+ADVHVcy8YZ4SlDgIAAAD9////AqCGAQAAAAAAFgAUoEw1q/YFe/2mhl35WzvUvk20QmNaMQMAAAAAABYAFEOKAfmCxUw9x1ml9mRxf+4vX07jAAAAAAEBH6CGAQAAAAAAFgAUoEw1q/YFe/2mhl35WzvUvk20QmMBCGsCRzBEAiATgsjBS2psH79D+R/inzDf2Wl7aYgys0Q75bW622ZzKQIgHn4CpXHxw8/HnhORMLaOI7KzSmiY3pb809NjONmyshUBIQP882q0ikSBm9dEwJcLJXMNSs8X434UyjgNKsUEef5jgwAAAA=="
machine2$ psbt_f=$(bitcoin-cli walletprocesspsbt $psbt_p | jq -r '.psbt')
machine2$ bitcoin-cli analyzepsbt $psbt_f
{
"inputs": [
{
"has_utxo": true,
"is_final": true,
"next": "extractor"
},
{
"has_utxo": true,
"is_final": true,
"next": "extractor"
}
],
"estimated_vsize": 189,
"estimated_feerate": 0.00000105,
"fee": 0.00000020,
"next": "extractor"
}
machine2$ psbt_hex=$(bitcoin-cli finalizepsbt $psbt_f | jq -r '.hex')
machine2$ bitcoin-cli -named sendrawtransaction hexstring=$psbt_hex
8bb0a0227b08596879502054bbdd0ace4cf8c686e32175aae2addbd9a1276337
We've used a PSBT to trustlessly gather money into a multisig!
📖 Could one of the users change the PSBT? Yes, but to limited utility. If the first signer changed the PSBT, the second signer, following the best practice of loking at the PSBT before he signs, would see the change, refuse to sign, and then go find a new business partner. If the second signer changed the PSBT, they would invalidate the first signer's signature. Finally, if the signatures were instead done in parallel (like in §8.1), the couldn't be combined, because they're no longer the same PSBT. This is what makes a PSBT funding of this sort trustless: it's only valid if all the involved people sign the same PSBT that they've agreed to beforehand.
Use a PSBT to CoinJoin
CoinJoin is another Bitcoin application that requires trustlessness. Here, you have a variety of parties who don't necessarily know each other joining money and getting it back.
The methodology for managing it with PSBTs is exactly the same as in the above example: a UTXO is created that the signatures agree to, as shown in the below pseudocode:
$ psbt=$(bitcoin-cli -named createpsbt inputs='''[ { "txid": "'$utxo_txid_1'", "vout": '$utxo_vout_1' }, { "txid": "'$utxo_txid_2'", "vout": '$utxo_vout_2' }, { "txid": "'$utxo_txid_3'", "vout": '$utxo_vout_3' } ]''' outputs='''{ "'$split1'": 1.7,"'$split2'": 0.93,"'$split3'": 1.4 }''')
The idea is that each user puts in their own UTXO, and each one receives a corresponding output. A real CoinJoin would involve many more users, say 20, and would thus require some sort of administrator to put together all the UTXOs correctly.
After creating a CoinJoin, the best way to manage it is to send out the base PSBT to all the parties (who could be numerous), and then have them each sign the PSBT and send back to a single party who will combine, finalize, and send.
:book: What is CoinJoin? CoinJoin is a methodology whereby a group of people can mix together their cryptocurrency, helping to reduce fungibility for all the coins. Each person puts in and takes out the same amount of coins (minus transaction fees) in a multi-person transaction that is simultaneously conducted by a large number of people. It's designed to be "trustless" so that the parties don't need to know or trust each other. A CoinJoin ultimately increases anonymity by making the coins hard to trace. The Wasabi Wallet and a number of "mixer" services support CoinJoin at the large-scale necessary for improved anonymity.
Summary: Using a Partially Signed Bitcoin Transaction
You've now seen how PSBTs are more than just a way to spend multisigs through the addition of two real-life examples: pooling funds and CoinJoining. These were all theoretically possible in classic Bitcoin by having multiple people sign carefully constructed transactions, but PSBTs make it standardized and simple.
:fire: What's the power of a PSBT? A PSBT allows for the creation of trustless transactions between multiple parties and multiple machines. If more than one party would need to fund a transaction, if more than one party would need to sign a transaction, or if a transaction needs to be created on one machine and signed on another, then a PSBT makes it simple without depending on the non-standardized partial signing mechanisms that used to exist before PSBT.
That last point, on creating a transaction on one machine and signing on another, is an element of PSBTs that we haven't gotten to yet. It's at the heart of hardware wallets, where you often want to create a transaction on a full node, then pass it on to a hardware wallet when a signature is required. That's the topic of the last section (and our fourth real-life example) in this chapter on PSBTs.
What's Next?
Before we get to hardware wallets we're going to take a detour in "Expanding Bitcoin Transactions with PSBTs" to §8.4: Creating Animated QR Codes, but if you prefer you can jump ahead to §8.5: Integrating with Hardware Wallets.