Counterpartyトークンをマルチシグで送受信する

今回やること

  1. APIを使ってマルチシグアドレスにトークンを送る
  2. APIを使ってマルチシグアドレスからトークンを送る
  3. APIを使ってマルチシグアドレス(P2SH)にトークンを送る
  4. 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