はじめに
上記記事では、bitcoinjs-libでSegwitシングルシグのトランザクションを作りました。
今回はマルチシグのトランザクションを作ってみましょう。
準備
アドレスの準備
https://qiita.com/shu-kob/items/63fa8aafa06cac1c7cda
こちらで作成したP2WSHアドレスを用意します。
P2WSH:
tb1q2adzgyzuy3msvjjfn3pkghf5fnwtwmxcglw3cuk5gvvv3ahmt63qqqa3n6
このアドレスに送金しておきます。
Faucet, フルノードから、何でも構いません。
ここでは、前回記事で送金をしております。
フルノード起動
こちらを参考にSignetフルノードを起動します。
鍵の準備
この記事でマルシシグアドレスを作成しましたが、そのときの拡張秘密鍵、拡張公開鍵を使用します。
$ node getKeys.js
mnemonic:
strong resource record gap leave diesel sleep gadget truck bonus omit slice remind sting tuna choice shoulder post turkey connect crane unhappy various rent
xpriv:
tprv8fcb9d93ydh7areMRdpi3sbbAqxwfJhQcis8ESquQhUUWxanngo8DM5yqCzE4TdFw6RmTbLUQZ42qzF8aBZkfnUxQs4qaHZwSw1NVBb24i3
xpub:
tpubDCJdJ3BJ81NnUKg9KHVJTHFhjsUspdtKC2TuWxtCpyGsMSqZR5ciPqhr1KvkWqgnFncdvmjU3VmKiumDnzQjg2YZvniC11PCz3pNcsHwBGY
$ node getKeys.js
mnemonic:
finish noble first teach visual document chef crash symbol calm spend frost sad edit tray damp chaos sad boost horn garbage neglect mention wrap
xpriv:
tprv8hAX4Q4XfvsMY6SRLFbVkQSmeTNSpBFB9NhWhPoP6t1G6TnMeBLsMYw6vm8zGQhiNL2GtyK9iaomcghRbB2KS1eqCyH6i353XDjTP5UZq7U
xpub:
tpubDDrZCp6mpJZ2RZUDDuG69p6tDUtNyWS5igJHyuqgX9oevx38GaATY3Yy6sSkdUd4GbKtGxoqE6Hn6jU3Svn243WDuQXxAALEGEh1BpGEfYQ
$ node getKeys.js
mnemonic:
museum room hidden group crew celery pumpkin net school metal frost light census tag replace welcome flock mimic funny diagram glow impose ecology scatter
xpriv:
tprv8gvJrJutiX5jVzgn29o9GbYSutadrUX4hyDS89ZvhE9LdMC53Lr8rrcKejX9zLnYKQKjKeihmM65jGMYdyGxz91HKGVckFZ4QC9DuJQyswn
xpub:
tpubDDcLzix8rtmQPTiZuoTjg1CZUv6a1ohyHGpDQfcE7VwjTqSqfjfj3MEBppwwDxrqAMdsvvaKfx6NzHhyeUsqfwAFbmYEx598Yvmpb5wh1B3
マルチシグの秘密鍵は本来は別々の人が持っているので、その想定で、別々に秘密鍵を書いたファイルを作成します。
ディレクトリはこれから作成するプログラムと同じです。
xpriv1.json
{
"xpriv": "tprv8fcb9d93ydh7areMRdpi3sbbAqxwfJhQcis8ESquQhUUWxanngo8DM5yqCzE4TdFw6RmTbLUQZ42qzF8aBZkfnUxQs4qaHZwSw1NVBb24i3"
}
xpriv2.json
{
"xpriv": "tprv8hAX4Q4XfvsMY6SRLFbVkQSmeTNSpBFB9NhWhPoP6t1G6TnMeBLsMYw6vm8zGQhiNL2GtyK9iaomcghRbB2KS1eqCyH6i353XDjTP5UZq7U"
}
xpriv3.json
{
"xpriv": "tprv8gvJrJutiX5jVzgn29o9GbYSutadrUX4hyDS89ZvhE9LdMC53Lr8rrcKejX9zLnYKQKjKeihmM65jGMYdyGxz91HKGVckFZ4QC9DuJQyswn"
}
拡張公開鍵は全て同じファイルに記述します。
xpubs.json
{
"xpub1": "tpubDCJdJ3BJ81NnUKg9KHVJTHFhjsUspdtKC2TuWxtCpyGsMSqZR5ciPqhr1KvkWqgnFncdvmjU3VmKiumDnzQjg2YZvniC11PCz3pNcsHwBGY",
"xpub2": "tpubDDrZCp6mpJZ2RZUDDuG69p6tDUtNyWS5igJHyuqgX9oevx38GaATY3Yy6sSkdUd4GbKtGxoqE6Hn6jU3Svn243WDuQXxAALEGEh1BpGEfYQ",
"xpub3": "tpubDDcLzix8rtmQPTiZuoTjg1CZUv6a1ohyHGpDQfcE7VwjTqSqfjfj3MEBppwwDxrqAMdsvvaKfx6NzHhyeUsqfwAFbmYEx598Yvmpb5wh1B3"
}
トランザクション作成
コードを書いていきます。
鍵は既に設定しているので、今回設定するのは、TXIDと宛先、送金額です。
multiSigTx.js
const bitcoin = require('bitcoinjs-lib');
const bip32 = require('bip32');
const bip39 = require('bip39');
const wif = require('wif');
const {xpub1, xpub2, xpub3} = require('./xpubs.json');
const MAINNET = bitcoin.networks.bitcoin;
const TESTNET = bitcoin.networks.testnet;
// let bitcoinNetwork = MAINNET;
let bitcoinNetwork = TESTNET;
const xpriv1 = require('./xpriv1.json').xpriv;
const xpriv2 = require('./xpriv2.json').xpriv;
const xpriv3 = require('./xpriv3.json').xpriv;
function getPrivkeyFromXpriv(xpriv) {
const privkeyNode = bitcoin.bip32.fromBase58(xpriv, bitcoinNetwork);
const privateKey_wif = privkeyNode.derive(1).derive(0).derive(0).derive(0).toWIF();
console.log("privateKey_wif:");
console.log(privateKey_wif);
const obj = wif.decode(privateKey_wif);
const privkey = bitcoin.ECPair.fromPrivateKey(obj.privateKey);
return privkey;
}
const privkey1 = getPrivkeyFromXpriv(xpriv1);
const privkey2 = getPrivkeyFromXpriv(xpriv2);
const privkey3 = getPrivkeyFromXpriv(xpriv3);
function getPubkeyFromXpub(xpub) {
const pubkeyNode = bitcoin.bip32.fromBase58(xpub, bitcoinNetwork);
const pubkey = pubkeyNode.derive(1).derive(0).derive(0).derive(0).publicKey;
return pubkey;
}
const pubkey1 = getPubkeyFromXpub(xpub1);
const pubkey2 = getPubkeyFromXpub(xpub2);
const pubkey3 = getPubkeyFromXpub(xpub3);
console.log(pubkey1)
const p2ms = bitcoin.payments.p2ms({
m: 2, pubkeys: [
Buffer.from(pubkey1, 'hex'),
Buffer.from(pubkey2, 'hex'),
Buffer.from(pubkey3, 'hex'),
], network: bitcoinNetwork})
console.log('Witness script:')
console.log(p2ms.output.toString('hex'))
const p2wsh = bitcoin.payments.p2wsh({redeem: p2ms, network: bitcoinNetwork})
console.log('P2WSH address')
console.log(p2wsh.address)
console.log("p2wsh.redeem.output")
console.log(p2wsh.redeem.output)
const psbt = new bitcoin.Psbt({ network: bitcoinNetwork });
psbt.addInput({
hash: '19fdd3ba91d7b7e2a05a49cdf20998334d5e5a774025e8cb51bfc2c4d06c364b',
index: 0,
witnessScript: p2wsh.redeem.output,
witnessUtxo: {
script: Buffer.from('0020' + bitcoin.crypto.sha256(p2ms.output).toString('hex'), 'hex'),
value: 70000,
},
});
psbt.addOutput({
address: "tb1qzwwvqxr2m8l0kt8trnnhaa48awtq45zauflz5y",
value: 40000,
});
psbt.addOutput({
address: "tb1q2adzgyzuy3msvjjfn3pkghf5fnwtwmxcglw3cuk5gvvv3ahmt63qqqa3n6",
value: 29811,
});
psbt.signInput(0, privkey1)
psbt.signInput(0, privkey2);
psbt.validateSignaturesOfInput(0, Buffer.from(pubkey1, 'hex'))
psbt.validateSignaturesOfInput(0, Buffer.from(pubkey2, 'hex'))
psbt.finalizeAllInputs();
console.log(JSON.stringify(psbt))
const txHex = psbt.extractTransaction().toHex();
console.log(txHex);
tprv8fcb9d93ydh7areMRdpi3sbbAqxwfJhQcis8ESquQhUUWxanngo8DM5yqCzE4TdFw6RmTbLUQZ42qzF8aBZkfnUxQs4qaHZwSw1NVBb24i3
privateKey_wif:
cVqL1ToSfwLw2xt6AB6bXoN51MrpTKj4RawFN682mD3zockVgqXD
privateKey_wif:
cRbPqcaZ9L9hvGkFkjyALCmb5hvwfkW8j4GhVpFqN1vvmNDGJZ61
privateKey_wif:
cStFWwyLBG6SGtNsqZ5MjnaLdYpsyzu5hp5U11WrL3ZBMPAWshdc
<Buffer 03 67 91 fd 09 6f 2a 83 f3 55 82 60 12 c9 a2 9b c0 7e a4 46 0c db c4 24 f5 7d 7b 18 1a 50 21 21 09>
Witness script:
5221036791fd096f2a83f355826012c9a29bc07ea4460cdbc424f57d7b181a502121092102f94b3a35c8464d8fe3a121e685b4f56adff15f5976f57f7b3e176b3b4707ba6121039ff1a14f03925083945d1158bcd7167979f8fb73dc8af80818a178aaaf64fa7553ae
P2WSH address
tb1q2adzgyzuy3msvjjfn3pkghf5fnwtwmxcglw3cuk5gvvv3ahmt63qqqa3n6
p2wsh.redeem.output
<Buffer 52 21 03 67 91 fd 09 6f 2a 83 f3 55 82 60 12 c9 a2 9b c0 7e a4 46 0c db c4 24 f5 7d 7b 18 1a 50 21 21 09 21 02 f9 4b 3a 35 c8 46 4d 8f e3 a1 21 e6 85 ... 55 more bytes>
{"data":{"inputs":[{"unknownKeyVals":[],"witnessUtxo":{"script":{"type":"Buffer","data":[0,32,87,90,36,16,92,36,119,6,74,73,156,67,100,93,52,76,220,183,108,216,71,221,28,114,212,67,24,200,246,251,94,162]},"value":70000},"finalScriptWitness":{"type":"Buffer","data":[4,0,71,48,68,2,32,65,233,81,111,242,84,71,249,155,230,86,100,0,244,228,149,29,210,109,36,127,224,165,8,170,116,123,243,15,137,158,172,2,32,78,84,97,146,72,212,201,128,98,249,123,155,232,39,104,37,119,27,116,207,106,217,129,215,63,193,187,67,5,168,121,137,1,72,48,69,2,33,0,149,244,110,225,175,243,24,4,202,69,197,15,149,227,243,244,69,56,239,215,102,218,38,177,136,106,47,127,94,185,138,105,2,32,107,101,33,227,32,158,205,128,5,55,74,184,50,228,177,130,18,240,98,216,210,153,110,79,188,211,50,184,173,156,140,80,1,105,82,33,3,103,145,253,9,111,42,131,243,85,130,96,18,201,162,155,192,126,164,70,12,219,196,36,245,125,123,24,26,80,33,33,9,33,2,249,75,58,53,200,70,77,143,227,161,33,230,133,180,245,106,223,241,95,89,118,245,127,123,62,23,107,59,71,7,186,97,33,3,159,241,161,79,3,146,80,131,148,93,17,88,188,215,22,121,121,248,251,115,220,138,248,8,24,161,120,170,175,100,250,117,83,174]}}],"outputs":[{"unknownKeyVals":[]},{"unknownKeyVals":[]}],"globalMap":{"unsignedTx":{}}}}
020000000001014b366cd0c4c2bf51cbe82540775a5e4d339809f2cd495aa0e2b7d791bad3fd190000000000ffffffff02409c000000000000160014139cc0186ad9fefb2ceb1ce77ef6a7eb960ad05d7374000000000000220020575a24105c2477064a499c43645d344cdcb76cd847dd1c72d44318c8f6fb5ea20400473044022041e9516ff25447f99be6566400f4e4951dd26d247fe0a508aa747bf30f899eac02204e54619248d4c98062f97b9be8276825771b74cf6ad981d73fc1bb4305a879890148304502210095f46ee1aff31804ca45c50f95e3f3f44538efd766da26b1886a2f7f5eb98a6902206b6521e3209ecd8005374ab832e4b18212f062d8d2996e4fbcd332b8ad9c8c5001695221036791fd096f2a83f355826012c9a29bc07ea4460cdbc424f57d7b181a502121092102f94b3a35c8464d8fe3a121e685b4f56adff15f5976f57f7b3e176b3b4707ba6121039ff1a14f03925083945d1158bcd7167979f8fb73dc8af80818a178aaaf64fa7553ae00000000
生TXをSignetフルノードでデコードします。
$ bcli decoderawtransaction 020000000001014b366cd0c4c2bf51cbe82540775a5e4d339809f2cd495aa0e2b7d791bad3fd190000000000ffffffff02409c000000000000160014139cc0186ad9fefb2ceb1ce77ef6a7eb960ad05d7374000000000000220020575a24105c2477064a499c43645d344cdcb76cd847dd1c72d44318c8f6fb5ea20400473044022041e9516ff25447f99be6566400f4e4951dd26d247fe0a508aa747bf30f899eac02204e54619248d4c98062f97b9be8276825771b74cf6ad981d73fc1bb4305a879890148304502210095f46ee1aff31804ca45c50f95e3f3f44538efd766da26b1886a2f7f5eb98a6902206b6521e3209ecd8005374ab832e4b18212f062d8d2996e4fbcd332b8ad9c8c5001695221036791fd096f2a83f355826012c9a29bc07ea4460cdbc424f57d7b181a502121092102f94b3a35c8464d8fe3a121e685b4f56adff15f5976f57f7b3e176b3b4707ba6121039ff1a14f03925083945d1158bcd7167979f8fb73dc8af80818a178aaaf64fa7553ae00000000
{
"txid": "0e907a700321294631a92fe0018c59692982ecd2f413e09abd2e32969acd83f9",
"hash": "be557ec49a7467da2b78cfdd228da80c44f4b5ff4c44698e1472e6203e353ea1",
"version": 2,
"size": 380,
"vsize": 189,
"weight": 755,
"locktime": 0,
"vin": [
{
"txid": "19fdd3ba91d7b7e2a05a49cdf20998334d5e5a774025e8cb51bfc2c4d06c364b",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"txinwitness": [
"",
"3044022041e9516ff25447f99be6566400f4e4951dd26d247fe0a508aa747bf30f899eac02204e54619248d4c98062f97b9be8276825771b74cf6ad981d73fc1bb4305a8798901",
"304502210095f46ee1aff31804ca45c50f95e3f3f44538efd766da26b1886a2f7f5eb98a6902206b6521e3209ecd8005374ab832e4b18212f062d8d2996e4fbcd332b8ad9c8c5001",
"5221036791fd096f2a83f355826012c9a29bc07ea4460cdbc424f57d7b181a502121092102f94b3a35c8464d8fe3a121e685b4f56adff15f5976f57f7b3e176b3b4707ba6121039ff1a14f03925083945d1158bcd7167979f8fb73dc8af80818a178aaaf64fa7553ae"
],
"sequence": 4294967295
}
],
"vout": [
{
"value": 0.00040000,
"n": 0,
"scriptPubKey": {
"asm": "0 139cc0186ad9fefb2ceb1ce77ef6a7eb960ad05d",
"hex": "0014139cc0186ad9fefb2ceb1ce77ef6a7eb960ad05d",
"reqSigs": 1,
"type": "witness_v0_keyhash",
"addresses": [
"tb1qzwwvqxr2m8l0kt8trnnhaa48awtq45zauflz5y"
]
}
},
{
"value": 0.00029811,
"n": 1,
"scriptPubKey": {
"asm": "0 575a24105c2477064a499c43645d344cdcb76cd847dd1c72d44318c8f6fb5ea2",
"hex": "0020575a24105c2477064a499c43645d344cdcb76cd847dd1c72d44318c8f6fb5ea2",
"reqSigs": 1,
"type": "witness_v0_scripthash",
"addresses": [
"tb1q2adzgyzuy3msvjjfn3pkghf5fnwtwmxcglw3cuk5gvvv3ahmt63qqqa3n6"
]
}
}
]
}
生TXをSignetフルノードからブロードキャストします。
$ bcli sendrawtransaction 020000000001014b366cd0c4c2bf51cbe82540775a5e4d339809f2cd495aa0e2b7d791bad3fd190000000000ffffffff02409c000000000000160014139cc0186ad9fefb2ceb1ce77ef6a7eb960ad05d7374000000000000220020575a24105c2477064a499c43645d344cdcb76cd847dd1c72d44318c8f6fb5ea20400473044022041e9516ff25447f99be6566400f4e4951dd26d247fe0a508aa747bf30f899eac02204e54619248d4c98062f97b9be8276825771b74cf6ad981d73fc1bb4305a879890148304502210095f46ee1aff31804ca45c50f95e3f3f44538efd766da26b1886a2f7f5eb98a6902206b6521e3209ecd8005374ab832e4b18212f062d8d2996e4fbcd332b8ad9c8c5001695221036791fd096f2a83f355826012c9a29bc07ea4460cdbc424f57d7b181a502121092102f94b3a35c8464d8fe3a121e685b4f56adff15f5976f57f7b3e176b3b4707ba6121039ff1a14f03925083945d1158bcd7167979f8fb73dc8af80818a178aaaf64fa7553ae00000000
0e907a700321294631a92fe0018c59692982ecd2f413e09abd2e32969acd83f9
Explorerで確認します。