Multi-signature Nested Segwit 2 of 4
To follow along this tutorial
|
Let’s create, fund and spend a 2 of 4 multi-signature with an embedded Segwit P2SH-P2WSH transaction.
Creating and Funding the P2SH
const bitcoin = require('bitcoinjs-lib')
const {alice, bob, carol, dave} = require('./wallets.json')
const network = bitcoin.networks.regtest
p2ms
payment method.const p2ms = bitcoin.payments.p2ms({
m: 2, pubkeys: [
Buffer.from(alice[1].pubKey, 'hex'),
Buffer.from(bob[1].pubKey, 'hex'),
Buffer.from(carol[1].pubKey, 'hex'),
Buffer.from(dave[1].pubKey, 'hex'),
], network})
console.log('Witness script:')
console.log(p2ms.output.toString('hex'))
console.log()
console.log('HASH160(SHA256(witness script)):')
const hash160sha256 = bitcoin.crypto.hash160(
Buffer.from('0020' + bitcoin.crypto.sha256(p2ms.output).toString('hex'),
'hex'))
console.log(hash160sha256.toString('hex')) (1)
console.log()
1 | This double hash is contained in the funding tx locking script |
decodescript SCRIPT
The asm field should contain:
02 03745c9aceb84dcdeddf2c3cdc1edb0b0b5af2f9bf85612d73fa6394758eaee35d 027efbabf425077cdbceb73f6681c7ebe2ade74a65ea57ebcf0c42364d3822c590 023a11cfcedb993ff2e7523f92e359c4454072a66d42e8b74b4b27a8a1258abddd 02e9d617f38f8c3ab9a6bde36ce991bafb295d7adba457699f8620c8160ec9e87a 04 OP_CHECKMULTISIG
const p2wsh = bitcoin.payments.p2wsh({redeem: p2ms, network})
const p2sh = bitcoin.payments.p2sh({redeem: p2wsh, network})
console.log('P2SH address:')
console.log(p2sh.address)
console.log()
sendtoaddress 2N4LnN5rp8JAmqE3LBVQhYEQg83piAF15sX 1
gettransaction TX_ID
Find the output index (or vout) under | .
Preparing the spending transaction
Let’s now prepare the spending transaction by setting input and output and having two people (private keys) to sign the transaction. In this tutorial, alice_1 and bob_1 will redeem the P2SH-P2WSH multi-signature and send the funds to alice_2 P2WPKH address.
const psbt = new bitcoin.Psbt({network})
.addInput({
hash: 'TX_ID',
index: TX_VOUT,
redeemScript: Buffer.from('0020' + bitcoin.crypto.sha256(p2ms.output).toString('hex'), 'hex'), (1)
witnessScript: p2wsh.redeem.output,
witnessUtxo: {
script: Buffer.from('a914' + hash160sha256.toString('hex') + '87', 'hex'), (2)
value: 1e8,
}
})
.addOutput({
address: alice[2].p2wpkh,
value: 999e5,
}) (3)
1 | Locking script of the UTXO we are spending, which is a P2WSH witness program, <00> + PushByte 20 + SHA256(witness script) |
2 | Hash160-SHA256 of the witness script, contained in a P2SH template |
3 | Output locking the funds to alice_2 P2WPKH address, leaving 100 000 satoshis as mining fees |
const keyPairAlice1 = bitcoin.ECPair.fromWIF(alice[1].wif, network)
const keyPairBob1 = bitcoin.ECPair.fromWIF(bob[1].wif, network)
psbt
.signInput(0, keyPairAlice1)
.signInput(0, keyPairBob1)
psbt.validateSignaturesOfInput(0, Buffer.from(alice[1].pubKey, 'hex'))
psbt.validateSignaturesOfInput(0, Buffer.from(bob[1].pubKey, 'hex'))
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 can see that the scriptSig contains the version byte 00
followed by a 32-bytes witness program, the sha256 of the witness script.
The hash160 of this scriptSig (asm version) should match the HASH160 contained in the P2SH UTXO locking script we are spending.
$ bx bitcoin160 '00205b07dcc35fc2b29db80be059e495c88f5b7609c1e3d888c14240678f00217b3d'
79b67d4c7bff512939e90e170ee9b969eb1203a8
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 stack. The witness, located in the txinwitness
field contains
-
An empty string that will convert to a dummy but mandatory
00
value due to a bug inOP_CHECKMULTISIG
-
Alice_1 and bob_1 signatures
-
The witness script