読者です 読者をやめる 読者になる 読者になる

Develop with pleasure!

福岡でCloudとかBlockchainとか。

Segwit導入におけるRPCの主要な変更点

Bitcoin Ruby Segwit

techmedia-think.hatenablog.com

先月リリースされたBitcoin Core 0.13.0にSegwitのコードがマージされtestnetで利用可能になったので、関連するRPCの変更点を見てみる。

RPCの変更点

getblock と getblockheader の変更点

出力内容に新しく以下の項目が追加されている。

  • strippedsize
    witnessデータを除外したブロックのサイズ
  • weight
    BIP-141でブロックサイズの上限が1MBから新しい制限に変更された。新しい制限は、witness関連のデータを除外したトランザクションシリアライズしたバイト単位のブロックサイズ=ベースサイズとBIP-144に定義されているシリアライズフォーマットでトランザクションシリアライズしたバイト単位のブロックサイズ=トータルサイズから、ベースサイズ✕3+トータルサイズ=block weightを算出しそのサイズが4,000,000以下というもの。weightにはそのblock weightが入る。
  • versionHex
    Segwitとは関係ないけど、BIP-9のソフトフォークの仕様でブロックのnVersionにversion bitが記載されてるようになったのに伴い、versionデータの16進表記が追加された。

getrawtransaction と decoderawtransaction の変更点

RPCの1つであるgetrawtransactionに引数1を付与してコールするとトランザクションのデータがJSON形式で表示され、decoderawtransactionもトランザクションデータをデコードしてJSON形式で表示するRPCである。

今回のSegwitの対応でその出力内容に新しく以下の項目が追加されている。

getmininginfo

getblock等に加わったblock weightを表示する以下の項目が追加された。

  • currentblockweight
    現在のブロックのweight

createwitnessaddress(新規)

↓のように16進表記のscriptPubKeyを引数にとり、そのスクリプトのwitness addressを作成するRPC。

createwitnessaddress <scriptPubkey>

戻り値は以下の2つ。

  • address
    (witness scriptのP2SHの)アドレスの値
  • witnessScript
    16進エンコードされたwitness scriptの文字列

引数のスクリプトからwitness scriptを実際に生成している処理が standard.cpp の↓

CScript GetScriptForWitness(const CScript& redeemscript)
{
    CScript ret;

    txnouttype typ;
    std::vector<std::vector<unsigned char> > vSolutions;
    if (Solver(redeemscript, typ, vSolutions)) {
        if (typ == TX_PUBKEY) {
            unsigned char h160[20];
            CHash160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(h160);
            ret << OP_0 << std::vector<unsigned char>(&h160[0], &h160[20]);
            return ret;
        } else if (typ == TX_PUBKEYHASH) {
           ret << OP_0 << vSolutions[0];
           return ret;
        }
    }
    uint256 hash;
    CSHA256().Write(&redeemscript[0], redeemscript.size()).Finalize(hash.begin());
    ret << OP_0 << ToByteVector(hash);
    return ret;
}

スクリプトがP2PKHなら、(P2PKHじゃなくてpay-to-pubkeyの場合はpubkeyのhash160してる)

OP_0 <公開鍵のハッシュ値>

というversion byteが0でwitness programがP2WPKHのwitness scriptになる。

スクリプトがP2SHなら

OP_0 <スクリプトのSHA-256ハッシュ>

というversion byteが0でwitness programがP2WSHのwitness scriptになる。

そうして生成したwitness scriptのhash160ハッシュして、P2SHのスクリプトハッシュとしてアドレスを生成している。

※ここで生成されるアドレスは、P2SHでネストされたP2WPKHかP2WSHになり純粋なP2WPKHやP2WSHではない。詳細はBIP-141で定義されているけどSegwitでは以下の4つのscriptPubKeyの形式がある中で、P2SHでネストされたものがこのRPCで生成される。

  • P2WPKH
  • P2WPKH nested in BIP16 P2SH
  • P2WSH
  • P2WSH nested in BIP16 P2SH
$ bitcoin-cli -regtest createwitnessaddress 21027ee57c6a7f4e1f4983fefc4a547b5a8576781ef87b7b06b6a3558b8e2461e0f4ac
{
  "address": "2NBXLLu2TBrF2N3V2AdiCofFJBqWFrbFUPZ",
  "witnessScript": "0014741459afe36cda3c132497ccbbe77be080515ac9"
}

なお、P2SHでネストされていない、P2WPKHなscriptPubkeyにBitcoinを送った際のトランザクションをパースして出力を確認すると↓のように、P2PKHやP2SHなどで表示されていたaddress情報は含まれない。

"vout": [
    {
      "value": 0.00900000,
      "n": 0,
      "scriptPubKey": {
        "asm": "0 015380566edf72c60106edd28871a1093ad42817",
        "hex": "0014015380566edf72c60106edd28871a1093ad42817",
        "type": "witness_v0_keyhash"
      }
  }
]

bitcoin-ruby使って自前で↑と同じP2WPKHのwitness scriptと P2WPKH nested in BIP16 P2SHなアドレスを生成するコードを書くと↓のようになる。

addwitnessaddress(新規)

createwitnessaddressがscriptPubKeyからアドレスとwitness scriptを生成するのに対し、addwitnessaddressは既存のP2PKHやP2SHのアドレスからwitness scriptを作成し、そこからwitnessアドレスを生成してウォレットに登録する。(ここで作られるアドレスもcreatewitnessaddressと同様P2SHでネストしたscriptPubkey)

addwitnessaddress "address"

↑のcreatewitnessaddressの引数に渡したscriptPubkeyの公開鍵から生成したアドレスは”mr6j4GmqxFq4eAmsDSUgGzSmdFcEyr6m4Z”なので、それを引数にaddwitnessaddressを実行すると

$ bitcoin-cli -regtest addwitnessaddress mr6j4GmqxFq4eAmsDSUgGzSmdFcEyr6m4Z 
2NBXLLu2TBrF2N3V2AdiCofFJBqWFrbFUPZ

同じアドレスが生成されているのが分かる。

rpcwallet.cppの以下のコードで、アドレスタイプ(P2PKHなのかP2SHなのか)別にアドレスからwitness scriptを作成してウォレットに追加している。

class Witnessifier : public boost::static_visitor<bool>
{
public:
    CScriptID result;

    bool operator()(const CNoDestination &dest) const { return false; }

    bool operator()(const CKeyID &keyID) {
        CPubKey pubkey;
        if (pwalletMain && pwalletMain->GetPubKey(keyID, pubkey)) {
            CScript basescript;
            basescript << ToByteVector(pubkey) << OP_CHECKSIG;
            CScript witscript = GetScriptForWitness(basescript);
            pwalletMain->AddCScript(witscript);
            result = CScriptID(witscript);
            return true;
        }
        return false;
    }

    bool operator()(const CScriptID &scriptID) {
        CScript subscript;
        if (pwalletMain && pwalletMain->GetCScript(scriptID, subscript)) {
            int witnessversion;
            std::vector<unsigned char> witprog;
            if (subscript.IsWitnessProgram(witnessversion, witprog)) {
                result = scriptID;
                return true;
            }
            CScript witscript = GetScriptForWitness(subscript);
            pwalletMain->AddCScript(witscript);
            result = CScriptID(witscript);
            return true;
        }
        return false;
    }
};

アドレスに登録されることで、ウォレット内のアドレスとしてUTXOの抽出などの対象になる。

signrawtransaction

トランザクションに署名をするsignrawtransactionも、署名データをwitnessデータにセットしたトランザクションを作成できるようになった。

P2WPKH宛てにBitcoinを送付したトランザクション

{
  "hex": "010000000153f944b5233653d7037126e0cfaa5236f40500b060331bd9d87985955a70a151000000006b483045022100f1798f45a0b7cffbce6f9939f4c176d50cef27c58b366b5ad66744fafa92b8d002202f16cb139d9ceabcf971d08dfc9dcbe711a37ad35beea23e92bc923934d08036012102bf15b363ff14a24f2c66221c3e84caeaacc846a17bae9657a33055af93b74857ffffffff01a0bb0d0000000000160014015380566edf72c60106edd28871a1093ad4281700000000",
  "txid": "ce8ab573226d7c3eb3a4a6d2d5867fb792993e5c284120d49005a9e1812caee2",
  "hash": "ce8ab573226d7c3eb3a4a6d2d5867fb792993e5c284120d49005a9e1812caee2",
  "size": 189,
  "vsize": 189,
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid": "51a1705a958579d8d91b3360b00005f43652aacfe0267103d7533623b544f953",
      "vout": 0,
      "scriptSig": {
        "asm": "3045022100f1798f45a0b7cffbce6f9939f4c176d50cef27c58b366b5ad66744fafa92b8d002202f16cb139d9ceabcf971d08dfc9dcbe711a37ad35beea23e92bc923934d08036[ALL] 02bf15b363ff14a24f2c66221c3e84caeaacc846a17bae9657a33055af93b74857",
        "hex": "483045022100f1798f45a0b7cffbce6f9939f4c176d50cef27c58b366b5ad66744fafa92b8d002202f16cb139d9ceabcf971d08dfc9dcbe711a37ad35beea23e92bc923934d08036012102bf15b363ff14a24f2c66221c3e84caeaacc846a17bae9657a33055af93b74857"
      },
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.00900000,
      "n": 0,
      "scriptPubKey": {
        "asm": "0 015380566edf72c60106edd28871a1093ad42817",
        "hex": "0014015380566edf72c60106edd28871a1093ad42817",
        "type": "witness_v0_keyhash"
      }
    }
  ],
  "blockhash": "05a1597c9e819dbe74d23dff6c8ca8524e7c99d0d91478c540b0606c9b08fc23",
  "confirmations": 1,
  "time": 1473600993,
  "blocktime": 1473600993
}

を入力にセットしたトランザクションを作成し、signrawtransactionに渡すと↓のようにwitnessデータに署名データがセットされる。

$ bitcoin-cli -regtest decoderawtransaction 01000000000101e2ae2c81e1a90590d42041285c3e9992b77f86d5d2a6a4b33e7c6d2273b58ace0000000000ffffffff0100350c00000000001976a914548742b7ee154defc3837876854490e8f6770c5e88ac02473044022069d189fd38ccd885074d8d5d4ff5797449451144e1c3bdeaf27e3a26db604bba02200dddabc2b20aaa68ef4175257a314a97eefb50edfa341b89f5537afd90f2c34f0121033d79f048c78bce024c5c163bdde233a11c24bafc6e100183bf9fe82c1d3b412200000000
{
  "txid": "119a735e396cd9691d0b3d3c9407a9fd3e2248f7bce41502058d89c7f69a5741",
  "hash": "8a6042c5c5333ea62dfda559f474204b59f359b1e69b86bc54611d6ca8c1c61d",
  "size": 194,
  "vsize": 113,
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid": "ce8ab573226d7c3eb3a4a6d2d5867fb792993e5c284120d49005a9e1812caee2",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "txinwitness": [
        "3044022069d189fd38ccd885074d8d5d4ff5797449451144e1c3bdeaf27e3a26db604bba02200dddabc2b20aaa68ef4175257a314a97eefb50edfa341b89f5537afd90f2c34f01", 
        "033d79f048c78bce024c5c163bdde233a11c24bafc6e100183bf9fe82c1d3b4122"
      ],
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.00800000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 548742b7ee154defc3837876854490e8f6770c5e OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914548742b7ee154defc3837876854490e8f6770c5e88ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "moDu6EtnGGpcTEkNZRJbytr9rJA4H49VRe"
        ]
      }
    }
  ]
}

signrawtransactionを使う分には特に意識しないけど、witnessなトランザクションではトランザクションへの署名時に生成するトランザクションのSIG_HASHの生成ロジックが変わっているので注意する必要がある。具体的な変更点はBIP-143に定義されている↓

techmedia-think.hatenablog.com

所感

  • createwitnessaddressやaddwitnessaddressで作られるアドレスがいずれもP2SHでネストしたアドレスなのは、従来のウォレットからの移行をスムーズにするための暫定の処置?
  • BIP-142にも書いてあるが、ブロックスペースを有効に使うにはP2SHでネストしたものではなくネイティブなP2WPKHやP2WSHを使うべき。
  • ネイティブなP2WPKHやP2WSHだとトランザクションの出力にアドレスが表記されないの、openassets-rubyでも対応する必要があるなー。
  • でもその前にbitcoin-rubyが果たしてSegwit対応するのか気になる。