Multi-signature Transactions
To read more
- BIP11 - M-of-N Standard Transactions
- bitcoinwiki.org - Multisignature
- P2P trading exchange based on Bitcoin multi-signature hodlhodl.com
Multi-signature (multisig) refers to requiring more than one key to authorize a Bitcoin transaction. It is generally used to divide up responsibility for possession of bitcoins.
Standard transactions on the Bitcoin network could be called "single-signature transactions" because transfers require only one signature — from the owner of the private key associated with the Bitcoin address. However, the Bitcoin network supports much more complicated transactions that require the signatures of multiple people before the funds can be
transferred. These are often referred to as M-of-N transactions.
The idea is that bitcoins become "encumbered" by providing addresses of multiple parties, thus requiring cooperation of those parties in order to do anything with them. These parties can be people, institutions or programmed scripts.
Multi-signature Legacy 2 of 4
To follow along this tutorial and enter the commands step-by-step
- Type
node
in a terminal aftercd
into./code
for a Javascript prompt- Open the Bitcoin Core GUI console or use
bitcoin-cli
for the Bitcoin Core commands- Use
bx
akaLibbitcoin-explorer
as a handy complement
Let's create a 2 of 4 multi-signature with a legacy P2SH transaction.
Creating and Funding the P2SH
Import libraries, test wallets and set the network
const bitcoin = require('bitcoinjs-lib')
const { alice, bob, carol, dave } = require('./wallets.json')
const network = bitcoin.networks.regtest
Prepare four key pairs.
const keyPairAlice0 = bitcoin.ECPair.fromWIF(alice[0].wif, network)
const keyPairBob0 = bitcoin.ECPair.fromWIF(bob[0].wif, network)
const keyPairCarol0 = bitcoin.ECPair.fromWIF(carol[0].wif, network)
const keyPairDave0 = bitcoin.ECPair.fromWIF(dave[0].wif, network)
And an other one for Alice that will redeem the multi-signature funds.
const keyPairAlice1 = bitcoin.ECPair.fromWIF(alice[1].wif, network)
const p2wpkhAlice1 = bitcoin.payments.p2wpkh({pubkey: keyPairAlice1.publicKey, network})
Create the locking script with the special p2ms
payment method.
const p2ms = bitcoin.payments.p2ms({
m: 2, pubkeys: [
keyPairAlice0.publicKey,
keyPairBob0.publicKey,
keyPairCarol0.publicKey,
keyPairDave0.publicKey], network})
Check the locking script.
$ decodescript [p2ms.output.toString('hex')]
"2 03745c9aceb84dcdeddf2c3cdc1edb0b0b5af2f9bf85612d73fa6394758eaee35d 027efbabf425077cdbceb73f6681c7ebe2ade74a65ea57ebcf0c42364d3822c590 023a11cfcedb993ff2e7523f92e359c4454072a66d42e8b74b4b27a8a1258abddd 02e9d617f38f8c3ab9a6bde36ce991bafb295d7adba457699f8620c8160ec9e87a 4 OP_CHECKMULTISIG"
Feed the p2sh
method with the special BitcoinJS p2ms
object.
The p2sh
method generates an object that contains the P2SH address.
const p2sh = bitcoin.payments.p2sh({redeem: p2ms, network})
Send 1 BTC to this P2SH address.
$ sendtoaddress 2NAjnUkVxAv34bnfpC1edhW5qstF2QHSUk5 1
Get the output index so that we have the outpoint (txid / vout).
Find the output index (or vout) under
details > vout
.
$ gettransaction "txid"
Preparing the spending transaction
Now let's prepare the spending transaction by setting input and output and having two people (private keys) to sign the
transaction. Here Alice_0 ad Bob_0 will redeem the P2SH multi-signature and send the funds to Alice_1 P2WPKH address.
Create a BitcoinJS transaction builder object.
const txb = new bitcoin.TransactionBuilder(network)
Create the input by referencing the outpoint of our P2SH funding transaction.
Create the output that will send the funds to Alice_1 P2WPKH address, leaving 100 000 satoshis as mining fees.
txb.addInput('TX_ID', TX_VOUT)
txb.addOutput(p2wpkhAlice1.address, 999e5)
Alice_0 and Bob_0 now sign the transaction.
Note that we need to provide the locking script as redeemScript third parameter of the sign
method.
// txb.sign(index, keyPair, redeemScript, sign.hashType, value, witnessScript)
txb.sign(0, keyPairAlice0, p2sh.redeem.output)
txb.sign(0, keyPairBob0, p2sh.redeem.output)
Build the transaction and get the raw hex serialization.
const tx = txb.build()
console.log('tx.toHex() ', tx.toHex())
Inspect the raw transaction with Bitcoin Core CLI, check that everything is correct.
$ decoderawtransaction "hexstring"
Broadcasting the transaction
It's time to broadcast the transaction via Bitcoin Core CLI.
$ sendrawtransaction "hexstring"
Inspect the transaction.
$ getrawtransaction "txid" true
Observations
We can see that the unlocking script contains
- a useless but mandatory
00
value due to a bug inOP_CHECKMULTISIG
- Alice_0 and Bob_0 signatures
- and our redeem script
Multi-signature Native Segwit 2 of 4
To follow along this tutorial and enter the commands step-by-step
- Type
node
in a terminal aftercd
into./code
for a Javascript prompt- Open the Bitcoin Core GUI console or use
bitcoin-cli
for the Bitcoin Core commands- Use
bx
akaLibbitcoin-explorer
as a handy complement
Let's create a 2 of 4 multi-signature with a native Segwit P2WSH transaction.
Creating and Funding the P2SH
Import libraries, test wallets and set the network
const bitcoin = require('bitcoinjs-lib')
const { alice, bob, carol, dave } = require('./wallets.json')
const network = bitcoin.networks.regtest
Prepare four key pairs.
const keyPairAlice0 = bitcoin.ECPair.fromWIF(alice[0].wif, network)
const keyPairBob0 = bitcoin.ECPair.fromWIF(bob[0].wif, network)
const keyPairCarol0 = bitcoin.ECPair.fromWIF(carol[0].wif, network)
const keyPairDave0 = bitcoin.ECPair.fromWIF(dave[0].wif, network)
And an other one for Alice that will redeem the multi-signature funds.
const keyPairAlice1 = bitcoin.ECPair.fromWIF(alice[1].wif, network)
const p2wpkhAlice1 = bitcoin.payments.p2wpkh({pubkey: keyPairAlice1.publicKey, network})
Create the locking script with the special p2ms
payment method.
const p2ms = bitcoin.payments.p2ms({
m: 2, pubkeys: [
keyPairAlice0.publicKey,
keyPairBob0.publicKey,
keyPairCarol0.publicKey,
keyPairDave0.publicKey], network})
Check the locking script.
$ decodescript [p2ms.output.toString('hex')]
"2 03745c9aceb84dcdeddf2c3cdc1edb0b0b5af2f9bf85612d73fa6394758eaee35d 027efbabf425077cdbceb73f6681c7ebe2ade74a65ea57ebcf0c42364d3822c590 023a11cfcedb993ff2e7523f92e359c4454072a66d42e8b74b4b27a8a1258abddd 02e9d617f38f8c3ab9a6bde36ce991bafb295d7adba457699f8620c8160ec9e87a 4 OP_CHECKMULTISIG"
Feed the p2wsh
method with the special BitcoinJS p2ms
object.
The p2wsh
method generates an object that contains the P2WSH address.
const p2wsh = bitcoin.payments.p2wsh({redeem: p2ms, network})
Send 1 BTC to this P2WSH address.
$ sendtoaddress bcrt1qtvraes6lc2efmwqtupv7f9wg3adhvzwpu0vg3s2zgpnc7qpp0v7sj6dkmu 1
Get the output index so that we have the outpoint (txid / vout).
Find the output index (or vout) under
details > vout
.
$ gettransaction "txid"
Preparing the spending transaction
Now let's prepare the spending transaction by setting input and output and having two people (private keys) to sign the
transaction. Here Alice_0 ad Bob_0 will redeem the P2WSH multi-signature and send the funds to Alice_1 P2WPKH address.
Create a BitcoinJS transaction builder object.
const txb = new bitcoin.TransactionBuilder(network)
Create the input by referencing the outpoint of our P2SH funding transaction.
Create the output that will send the funds to Alice_1 P2WPKH address, leaving 100 000 satoshis as mining fees.
txb.addInput('TX_ID', TX_VOUT)
txb.addOutput(p2wpkhAlice1.address, 999e5)
Alice_0 and Bob_0 now sign the transaction. Note that, because we are doing a P2WSH, we need to provide the locking script as witnessScript sixth parameter of the sign
method, as well as the input value.
// txb.sign(index, keyPair, redeemScript, sign.hashType, value, witnessScript)
txb.sign(0, keyPairAlice0, null, null, 1e8, p2wsh.redeem.output)
txb.sign(0, keyPairBob0, null, null, 1e8, p2wsh.redeem.output)
Build the transaction and get the raw hex serialization.
const tx = txb.build()
console.log('tx.toHex() ', tx.toHex())
Inspect the raw transaction with Bitcoin Core CLI, check that everything is correct.
$ decoderawtransaction "hexstring"
Broadcasting the transaction
It's time to broadcast the transaction via Bitcoin Core CLI.
$ sendrawtransaction "hexstring"
Inspect the transaction.
$ getrawtransaction "txid" true
Observations
We can see that the scriptSig unlocking script is empty and instead all the data are located in the txinwitness
field
- an empty string that will convert to a useless but mandatory
00
value due to a bug inOP_CHECKMULTISIG
- Alice_0 and Bob_0 signatures
- and our witness script
8.3: Multi-signature Embedded Segwit 2 of 4
To follow along this tutorial and enter the commands step-by-step
- Type
node
in a terminal aftercd
into./code
for a Javascript prompt- Open the Bitcoin Core GUI console or use
bitcoin-cli
for the Bitcoin Core commands- Use
bx
akaLibbitcoin-explorer
as a handy complement
Let's create a 2 of 4 multi-signature with an embedded Segwit P2SH-P2WSH transaction.
Creating and Funding the P2SH
Import libraries, test wallets and set the network
const bitcoin = require('bitcoinjs-lib')
const { alice, bob, carol, dave } = require('./wallets.json')
const network = bitcoin.networks.regtest
Prepare four key pairs.
const keyPairAlice0 = bitcoin.ECPair.fromWIF(alice[0].wif, network)
const keyPairBob0 = bitcoin.ECPair.fromWIF(bob[0].wif, network)
const keyPairCarol0 = bitcoin.ECPair.fromWIF(carol[0].wif, network)
const keyPairDave0 = bitcoin.ECPair.fromWIF(dave[0].wif, network)
And an other one for Alice that will redeem the multi-signature funds.
const keyPairAlice1 = bitcoin.ECPair.fromWIF(alice[1].wif, network)
const p2wpkhAlice1 = bitcoin.payments.p2wpkh({pubkey: keyPairAlice1.publicKey, network})
Create the locking script with the special p2ms
payment method.
const p2ms = bitcoin.payments.p2ms({
m: 2, pubkeys: [
keyPairAlice0.publicKey,
keyPairBob0.publicKey,
keyPairCarol0.publicKey,
keyPairDave0.publicKey], network})
Check the locking script.
$ decodescript [p2ms.output.toString('hex')]
"2 03745c9aceb84dcdeddf2c3cdc1edb0b0b5af2f9bf85612d73fa6394758eaee35d 027efbabf425077cdbceb73f6681c7ebe2ade74a65ea57ebcf0c42364d3822c590 023a11cfcedb993ff2e7523f92e359c4454072a66d42e8b74b4b27a8a1258abddd 02e9d617f38f8c3ab9a6bde36ce991bafb295d7adba457699f8620c8160ec9e87a 4 OP_CHECKMULTISIG"
Feed the p2sh
method with the p2wsh
object.
const p2wsh = bitcoin.payments.p2wsh({redeem: p2ms, network})
const p2sh = bitcoin.payments.p2sh({redeem: p2wsh, network})
Send 1 BTC to this P2SH address.
$ sendtoaddress 2N4LnN5rp8JAmqE3LBVQhYEQg83piAF15sX 1
Get the output index so that we have the outpoint (txid / vout).
Find the output index (or vout) under
details > vout
.
$ gettransaction "txid"
Preparing the spending transaction
Now let's prepare the spending transaction by setting input and output and having two people (private keys) to sign the
transaction. Here Alice_0 ad Bob_0 will redeem the P2SH-P2WSH multi-signature and send the funds to Alice_1 P2WPKH address.
Create a BitcoinJS transaction builder object.
const txb = new bitcoin.TransactionBuilder(network)
Create the input by referencing the outpoint of our P2SH funding transaction.
Create the output that will send the funds to Alice_1 P2WPKH address, leaving 100 000 satoshis as mining fees.
txb.addInput('TX_ID', TX_VOUT)
txb.addOutput(p2wpkhAlice1.address, 999e5)
Alice_0 and Bob_0 now sign the transaction.
Note that, because we are doing a P2SH-P2WSH, we need to provide the locking script as the redeemScript third parameter,
the same script as the witnessScript sixth parameter, as well as the input value.
// txb.sign(index, keyPair, redeemScript, sign.hashType, value, witnessScript)
txb.sign(0, keyPairAlice0, p2sh.redeem.output, null, 1e8, p2wsh.redeem.output)
txb.sign(0, keyPairBob0, p2sh.redeem.output, null, 1e8, p2wsh.redeem.output)
Build the transaction and get the raw hex serialization.
const tx = txb.build()
console.log('tx.toHex() ', tx.toHex())
Inspect the raw transaction with Bitcoin Core CLI, check that everything is correct.
$ decoderawtransaction "hexstring"
Broadcasting the transaction
It's time to broadcast the transaction via Bitcoin Core CLI.
$ sendrawtransaction "hexstring"
Inspect the transaction.
$ getrawtransaction "txid" true
Observations
We can see that the scriptSig is a special unlocking script that contains the version byte 00
followed by a 32-bytes
witness program. This script has to match the HASH160 in the P2SH UTXO we are spending.
Verify the unlocking script HASH160.
$ bx bitcoin160 '00205b07dcc35fc2b29db80be059e495c88f5b7609c1e3d888c14240678f00217b3d'
or
bitcoin.crypto.hash160(Buffer.from('00205b07dcc35fc2b29db80be059e495c88f5b7609c1e3d888c14240678f00217b3d', 'hex')).toString('hex')
After checking hash equality, the script interpreter recognize that it is actually a Segwit transaction thanks to the
version byte and triggers execution of the witness data.
The witness, located in the txinwitness
field contains
- an empty string that will convert to a useless but mandatory
00
value due to a bug inOP_CHECKMULTISIG
- Alice_0 and Bob_0 signatures
- and our witness script
Comments