UTXO Consolidation (3 inputs, 1 output) - Legacy P2PKH
To follow along this tutorial
|
Let’s do UTXO consolidation, also called an aggregating transaction.
The idea is that a single signer aggregates many small UTXOs into a single big UTXO. This represents the real-world equivalent of exchanging a pile of coins and currency notes for a single larger note.
In legacy P2PKH transaction, each input contains a signature, which quickly increases the size of your transactions if you need to spend multiple UTXOs, resulting in high fees. Consolidation allows to group multiple UTXOs in one, which will result in less fees when spent.
Reducing the number of UTXOs also frees the UTXOs database (chainstate), making it easier to run a full node, marginally improving Bitcoin’s decentralisation and overall security, which is always nice.
Finally, consolidation gives an opportunity to update the addresses you use for your UTXOs, for example to roll keys over, switch to multisig, or switch to Segwit bech32 addresses.
For this example we will consolidate three P2PKH UTXOs (referencing each of them with three inputs) to one Segwit P2WPKH UTXO.
Creating UTXOs to spend
First we need to create three UTXOs.
UTXO consolidation makes sense if we have multiple payments on a single address, or multiple payments on different addresses controlled by a single person or entity.
Let’s use different P2PKH addresses, all controlled by Alice. We will have one payment to alice_1 P2PKH address, one to alice_2 P2PKH address and one to alice_3 P2PKH address.
sendmany
.sendmany "" '{"n4SvybJicv79X1Uc4o3fYXWGwXadA53FSq":0.33, "mgZt5Fqzszdwf8hDgZt3mUf7js611aKRPc":0.33, "n3ZLcnCtfRucM4WLnXqukm9bTdb1PWeETk":0.33}'
We have now three UTXOs locked with Alice public keys hash. In order to spend it, we refer to it with the transaction id (txid) and the output index (vout), also called outpoint.
gettransaction "txid"
Find the output index (or vout) under
Each UXTO appears twice, once in the 'send' category, once in the 'receive' category.
You may think that the indexes will be 0, 1 and 2. If so you are forgetting that there is also a change UTXO, that can be located at any position.
The command gettransaction
doesn’t display the change UTXO, but a decoderawtransaction
does.
Creating the UTXO consolidation transaction
const bitcoin = require('bitcoinjs-lib')
const { alice } = require('./wallets.json')
const network = bitcoin.networks.regtest
Now let’s spend the UTXOs with BitcoinJS, consolidating them into a single P2WPKH UTXO.
const keyPairAlice1 = bitcoin.ECPair.fromWIF(alice[1].wif, network)
const keyPairAlice2 = bitcoin.ECPair.fromWIF(alice[2].wif, network)
const keyPairAlice3 = bitcoin.ECPair.fromWIF(alice[3].wif, network)
const nonWitnessUtxo = Buffer.from('TX_HEX', 'hex') (1)
1 | Get TX_HEX with "getrawtransaction TX_ID" |
const psbt = new bitcoin.Psbt({network})
.addInput({
hash: 'TX_ID',
index: TX_OUT,
nonWitnessUtxo
})
.addInput({
hash: 'TX_ID',
index: TX_OUT,
nonWitnessUtxo
})
.addInput({
hash: 'TX_ID',
index: TX_OUT,
nonWitnessUtxo
}) (1)
.addOutput({
address: alice[1].p2wpkh,
value: 989e5,
}) (2)
1 | Three inputs referencing the 0.33BTC UTXOs just created |
2 | P2WPKH output with an amount of 0.99 BTC |
The miner fee is calculated by subtracting the outputs from the inputs. |
Alice adds the correct signature to each input.
For each input, we have to select the right keypair to produce signatures that satisfy the locking scripts of the three UTXOs we are spending.
We have this information by calling gettransaction
.
This is tricky because the order of the UTXOs in a transaction varies.
For example
txb.sign(0, keyPairAlice1)
txb.sign(1, keyPairAlice3)
txb.sign(2, keyPairAlice2)
psbt.signInput(0, keyPairAlice1)
psbt.signInput(1, keyPairAlice2)
psbt.signInput(2, keyPairAlice3)
psbt.validateSignaturesOfInput(0)
psbt.validateSignaturesOfInput(1)
psbt.validateSignaturesOfInput(2)
psbt.finalizeAllInputs()
console.log('Transaction hexadecimal:')
console.log(psbt.extractTransaction().toHex())
decoderawtransaction TX_HEX
Broadcasting the transaction
sendrawtransaction TX_HEX
getrawtransaction TX_ID true
Observations
We note that each input contains a 71 bytes signature (on average), resulting in a large and costly transaction. Spending Segwit UTXOs results in much smaller transaction since the input scripts are empty and Segwit data are discounted. Also, the implementation of Schnorr signatures into Bitcoin will allow to aggregate all those signatures into a single one.