Counterpartyトークンをマルチシグで送受信する
今回やること
- APIを使ってマルチシグアドレスにトークンを送る
- APIを使ってマルチシグアドレスからトークンを送る
- APIを使ってマルチシグアドレス(P2SH)にトークンを送る
- APIを使ってマルチシグアドレス(P2SH)からトークンを送る
はじめに
Counterpartyd、Counterwalletにはトークンをマルチシグ管理するための機能があるので、通常のトークン送信と同じような方法で送受信ができます。
P2SHアドレスの作成と署名は、現在Counterwalletに機能がないためBitcoinJSを利用します。
1. APIを使ってマルチシグアドレスにトークンを送る
APIパラメータ
(1) destination
1_addressA_addressB_2 の形式でマルチシグを表現します。現在以下5種類をサポートしています。(1-of-2, 2-of-2, 1-of-3, 2-of-3, 3-of-3)
(2) pubkey
ビットコインアドレスのpubkeyを設定します。ブロックチェーンにpubkeyがある場合(一度でもブロードキャストされている場合)は設定しなくてもよいです。(ただし過去のトランザクションデータの検索が必要になるので、設定しておいた方がレスポンスは早いです)
(3) use_enhanced_send
これはfalseに設定してください。(最近のバージョンから使えるenhanced_sendに対応してません。ちなみに現在のCounterwalletではtrueが設定されていて画面からトークンが送信できないバグがあります。enhanced_sendのプロトコルレベルでマルチシグが利用できるか(想定しているか)は調べてません)
パラメータ例
{ "method": "create_send", "params": { "source": "n4kCdhxto4RkLP7NA2DPT41ExfHTHBa7ie", "destination": "2_n2LX8EHbqkfvW4kJckZjmTgiSzH2kUZzAB_n4kCdhxto4RkLP7NA2DPT41ExfHTHBa7ie_2", "quantity": 1000000, "asset": "XCP", "pubkey": [ "02f9e5e548e9e11d376da691871353bb2ceeba9d82548ab6a81d297d2787516a14", "028383af700eef090f13e7e2119c388c1934f9f4b1ecad1ac21125ad0ee349933b" ], "allow_unconfirmed_inputs": true, "fee_per_kb": 472064, "use_enhanced_send": false }, "jsonrpc": "2.0", "id": 0 }
API実行結果
$ curl -X POST http://localhost:14000/api/ --user user:password -H 'Content-Type: application/json; charset=UTF-8' -H 'Accept: application/json, text/javascript' --data-binary '{"method":"create_send","params":{"source":"n4kCdhxto4RkLP7NA2DPT41ExfHTHBa7ie","destination":"2_n2LX8EHbqkfvW4kJckZjmTgiSzH2kUZzAB_n4kCdhxto4RkLP7NA2DPT41ExfHTHBa7ie_2","quantity":100000000,"asset":"XCP", "pubkey":["02f9e5e548e9e11d376da691871353bb2ceeba9d82548ab6a81d297d2787516a14", "028383af700eef090f13e7e2119c388c1934f9f4b1ecad1ac21125ad0ee349933b"],"allow_unconfirmed_inputs":true,"fee_per_kb":472064,"use_enhanced_send":false}, "jsonrpc": "2.0", "id": 0}' {"result": "0100000001e995b3dd7ded5a644d9cef5efeeda798015e5fee3df18b02d501edd9f944c867010000001976a914fecc451e74d541dc1a4505d9ab9d560c07267e0188acffffffff03781e000000000000475221028383af700eef090f13e7e2119c388c1934f9f4b1ecad1ac21125ad0ee349933b2102f9e5e548e9e11d376da691871353bb2ceeba9d82548ab6a81d297d2787516a1452ae00000000000000001e6a1c65e4ede30a5290d4a8e8e57d3d32a6e8826f012ede3977c40bd6758787404101000000001976a914fecc451e74d541dc1a4505d9ab9d560c07267e0188ac00000000", "jsonrpc": "2.0", "id": 0}
承認後の残高確認
$ curl -X POST http://localhost:14000/api/ --user user:password -H 'Content-Type: application/json; charset=UTF-8' -H 'Accept: application/json, text/javascript' --data-binary '{"jsonrpc":"2.0", "id":0, "method":"get_balances", "params":{"filters": {"field": "address", "op": "==", "value": "2_n2LX8EHbqkfvW4kJckZjmTgiSzH2kUZzAB_n4kCdhxto4RkLP7NA2DPT41ExfHTHBa7ie_2"}}}' {"result": [{"quantity": 1, "asset": "XCP", "address": "2_n2LX8EHbqkfvW4kJckZjmTgiSzH2kUZzAB_n4kCdhxto4RkLP7NA2DPT41ExfHTHBa7ie_2"}], "id": 0, "jsonrpc": "2.0"}
2. APIを使ってマルチシグアドレスからトークンを送る
APIパラメータ
(1) source
上記と同様に 1_addressA_addressB_2 の形式でマルチシグを表現します。
API実行結果
$ curl -X POST http://localhost:14000/api/ --user user:password -H 'Content-Type: application/json; charset=UTF-8' -H 'Accept: application/json, text/javascript' --data-binary '{"method":"create_send","params":{"source":"2_n2LX8EHbqkfvW4kJckZjmTgiSzH2kUZzAB_n4kCdhxto4RkLP7NA2DPT41ExfHTHBa7ie_2","destination":"n4kCdhxto4RkLP7NA2DPT41ExfHTHBa7ie","quantity":100000000,"asset":"XCP", "allow_unconfirmed_inputs":true,"fee_per_kb":472064}, "jsonrpc": "2.0", "id": 0}' {"result": "0100000001f9ea921f702633733fdbff3758000fc7478ce1f91b3ae16b20eecff86e2950f900000000475221028383af700eef090f13e7e2119c388c1934f9f4b1ecad1ac21125ad0ee349933b2102f9e5e548e9e11d376da691871353bb2ceeba9d82548ab6a81d297d2787516a1452aeffffffff020000000000000000306a2e3e374eef6d20de55f34da21729e57c9a090db808d803719d684bef3475ca77b131a6f5171042fc264fc7bc0363c95790960000000000475221028383af700eef090f13e7e2119c388c1934f9f4b1ecad1ac21125ad0ee349933b2102f9e5e548e9e11d376da691871353bb2ceeba9d82548ab6a81d297d2787516a1452ae00000000", "id": 0, "jsonrpc": "2.0"}
上記のhexを2つのアドレスの秘密鍵で署名してブロードキャストできます。(CounterwalletのSign機能を利用できます)
3. APIを使ってマルチシグアドレス(P2SH)にトークンを送る
P2SHアドレス作成
P2SHアドレスの作成は、現在Counterwalletに機能がないためBitcoinJSを利用します。
var bitcoin = require('bitcoinjs-lib') var pubKeys = [ '02f9e5e548e9e11d376da691871353bb2ceeba9d82548ab6a81d297d2787516a14', '028383af700eef090f13e7e2119c388c1934f9f4b1ecad1ac21125ad0ee349933b' ].map(function (hex) { return Buffer.from(hex, 'hex') }) var redeemScript = bitcoin.script.multisig.output.encode(2, pubKeys) var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) var address = bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.testnet) console.log(address) // 2MvEVnmi8yVvvJFugHBQNhyJpNpQdSYCYUR
API実行結果
$ curl -X POST http://localhost:14000/api/ --user user:password -H 'Content-Type: application/json; charset=UTF-8' -H 'Accept: application/json, text/javascript' --data-binary '{"method":"create_send","params":{"source":"n4kCdhxto4RkLP7NA2DPT41ExfHTHBa7ie","destination":"2MvEVnmi8yVvvJFugHBQNhyJpNpQdSYCYUR","quantity":100000000,"asset":"XCP", "allow_unconfirmed_inputs":true,"fee_per_kb":472064}, "jsonrpc": "2.0", "id": 0}' {"result": "0100000001817f90d369aae1c1753a4d675ad9d30aa58b10b6c81d91ecc013bacdca39b18a020000001976a914fecc451e74d541dc1a4505d9ab9d560c07267e0188acffffffff020000000000000000306a2e3b55ea1002c5b45e1837e8d3354db858412f36ab5f5100335b76c8ef06a6833b375f48de208a833118580e84d137a4b3a200000000001976a914fecc451e74d541dc1a4505d9ab9d560c07267e0188ac00000000", "id": 0, "jsonrpc": "2.0"}
承認後の残高確認
$ curl -X POST http://localhost:14000/api/ --user user:password -H 'Content-Type: application/json; charset=UTF-8' -H 'Accept: application/json, text/javascript' --data-binary '{"jsonrpc":"2.0", "id":0, "method":"get_balances", "params":{"filters": {"field": "address", "op": "==", "value": "2MvEVnmi8yVvvJFugHBQNhyJpNpQdSYCYUR"}}}' {"result": [{"quantity": 1, "asset": "XCP", "address": "2MvEVnmi8yVvvJFugHBQNhyJpNpQdSYCYUR"}], "id": 0, "jsonrpc": "2.0"}
4. APIを使ってマルチシグアドレス(P2SH)からトークンを送る
APIを利用して、未サイントランザクションhexを生成して、BitcoinJSを利用してサインします。
API実行結果
$curl -X POST http://localhost:14000/api/ --user user:password -H 'Content-Type: application/json; charset=UTF-8' -H 'Accept: application/json, text/javascript' --data-binary '{"method":"create_send","params":{"source":"2MvEVnmi8yVvvJFugHBQNhyJpNpQdSYCYUR","destination":"n4kCdhxto4RkLP7NA2DPT41ExfHTHBa7ie","quantity":100000000,"asset":"XCP", "allow_unconfirmed_inputs":true,"fee_per_kb":472064}, "jsonrpc": "2.0", "id": 0}' {"result": "01000000010e9a3cedf9e67c5083f01be362e57d54e3bc850266f06e0d2a8523e3226693770000000017a91420c526de2efaf3f6ffe39c31eeb9379b59fcbb2287ffffffff020000000000000000306a2e502965d769b0e92678d81371298a0e7b85dcf613a57500432f8ab61f6791b36093b8c459f7113cbf6f6752c43905173c0d000000000017a91420c526de2efaf3f6ffe39c31eeb9379b59fcbb228700000000", "id": 0, "jsonrpc": "2.0"}
BitcoinJSを利用して署名
var bitcoin = require('bitcoinjs-lib'); var pubKeys = [ '02f9e5e548e9e11d376da691871353bb2ceeba9d82548ab6a81d297d2787516a14', '028383af700eef090f13e7e2119c388c1934f9f4b1ecad1ac21125ad0ee349933b' ].map(function (hex) { return Buffer.from(hex, 'hex') }) var privKeys = [ 'cNGfHoZMstBj6y7Hag5Yio7VgX6cFGNX6sSj1wgT8Gm5MuoZ1kXs', 'cR7HBcJqAXGyBPHZ8nH2YQiek7ysVLGzc1NkF2k85HGBgEpjpWJA' ].map(function (wif) { return bitcoin.ECPair.fromWIF(wif, bitcoin.networks.testnet) }) var redeemScript = bitcoin.script.multisig.output.encode(2, pubKeys); var tx = bitcoin.Transaction.fromHex('01000000010e9a3cedf9e67c5083f01be362e57d54e3bc850266f06e0d2a8523e3226693770000000017a91420c526de2efaf3f6ffe39c31eeb9379b59fcbb2287ffffffff020000000000000000306a2e502965d769b0e92678d81371298a0e7b85dcf613a57500432f8ab61f6791b36093b8c459f7113cbf6f6752c43905173c0d000000000017a91420c526de2efaf3f6ffe39c31eeb9379b59fcbb228700000000'); var txb = bitcoin.TransactionBuilder.fromTransaction(tx, bitcoin.networks.testnet); var rawSignedTransaction; txb.tx.ins.forEach(function(input, i) { txb.inputs[i] = {}; txb.sign(i, privKeys[0], redeemScript); txb.sign(i, privKeys[1], redeemScript); try { rawSignedTransaction = txb.build().toHex(); console.log('Successfully signed.'); } catch (e) { if ('Transaction is missing signatures' === e.message) { // Normal, because every inputs are not signed yet. rawSignedTransaction = txb.buildIncomplete().toHex(); } else if ('Not enough signatures provided' === e.message) { console.log('Not enough signatures provided'); rawSignedTransaction = txb.buildIncomplete().toHex(); } else { console.log(e); } } }); console.log(rawSignedTransaction); // 01000000010e9a3cedf9e67c5083f01be362e57d54e3bc850266f06e0d2a8523e32266937700000000d90047304402206f8c21a72173fef6c6a1de85b8dc0f89e3552a4aad797ed529f25bdcbe558f490220393a8e0240e51832cae9ce123ba784a44888d53343b70c651da647057a80a6e9014730440220585e686b97ea3a0368c0037dab398b7e175d925a1192ca08b64d28abf8702c5b0220296b45dc90cf0b8267a51b42b00c7979592bca214cb009f358183ca32f4dfb4b0147522102f9e5e548e9e11d376da691871353bb2ceeba9d82548ab6a81d297d2787516a1421028383af700eef090f13e7e2119c388c1934f9f4b1ecad1ac21125ad0ee349933b52aeffffffff020000000000000000306a2e502965d769b0e92678d81371298a0e7b85dcf613a57500432f8ab61f6791b36093b8c459f7113cbf6f6752c43905173c0d000000000017a91420c526de2efaf3f6ffe39c31eeb9379b59fcbb228700000000