Develop with pleasure!

福岡でCloudとかBlockchainとか。

P2WPKHベースのアカウント導出スキームを定義したBIP-84

HDウォレットでSegwitのアドレスを導出する仕様について、今まではP2SHでネストしたP2WPKHの導出スキームを定義したBIP-49のみだったが↓

techmedia-think.hatenablog.com

今回ネイティブのP2WKHのアドレスをHDウォレットで導出するスキームがBIP-84としてTrezorのPavol Rusnakから提案された。

https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki

仕様自体はシンプルでP2WPKHのアドレスを導出する区分としてBIP-44のpurposeの階層の値をこのBIPの値84を使った強化導出にするだけで、導出スキーム自体は既存のスキームと変わりない。

概要

このBIPではP2WPKH(BIP-173)のsegregated witnessトランザクションのシリアライゼーションフォーマットを使用するHDウォレットの導出スキームを定義する。

動機

P2WPKHのトランザクションを作る場合、共通の導出スキームを持つ必要がある。これによりユーザーは同じマスタードシードを持つもしくは単一アカウントを異なるHDウォレットでシームレスに扱うことができるようになる。

このBIPと互換性のあるウォレットを使用すれば、ユーザーはsegregated witnessアカウントを検出し適切に処理することが保証される。

考慮事項

BIP-49の考慮事項と同じ。

仕様

このBIPはBIP-32のルートアカウントに基づき、複数の決定性アドレスを導出するために必要な2つのステップを定義する。

公開鍵の導出

このBIPでは、ルートアカウントから公開鍵を導出するためBIP-44やBIP-49で定義されているのと同じアカウント構造を使用するが、トランザクションシリアライズ方法が異なることを示すためpurposeの値に異なる値を使用する点が唯一異なる。

m / purpose' / coin_type' / account' / change / address_index

パス階層のpurposeには84'を使用する。他の階層はBIP-44とBIP-49と同じものが使われる。

アドレスの導出

上記で計算した公開鍵からP2WPKHアドレスを導出するために、BIP-141で定義されているカプセル化を使う。

witness:      <署名> <公開鍵>
scriptSig:    (空)
scriptPubKey: 0 <20バイトの公開鍵ハッシュ>
              (0x0014{20バイトの公開鍵ハッシュ})

拡張鍵バージョン

拡張鍵をシリアライズする際、この方式では、代替version byteを使用する。拡張公開鍵は0x04b24746を使用しzpubというプレフィックスを生成し、拡張秘密鍵0x04b2430cを使用しzprvというプレフィックスを生成する。Testnetでは、0x045f1cf6vpub0x045f18bcvprvを使用する。

追加の登録済みversion byteはSLIP-0132にリストされている。

後方互換

このBIPのには考慮事項に記載されているように後方互換性はない。互換性のないウォレットの場合、対象のアカウントが見つけられず何かおかしいことに気付くでしょう。

Test Vecotr

mnemonic = abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
rootpriv = zprvAWgYBBk7JR8Gjrh4UJQ2uJdG1r3WNRRfURiABBE3RvMXYSrRJL62XuezvGdPvG6GFBZduosCc1YP5wixPox7zhZLfiUm8aunE96BBa4Kei5
rootpub  = zpub6jftahH18ngZxLmXaKw3GSZzZsszmt9WqedkyZdezFtWRFBZqsQH5hyUmb4pCEeZGmVfQuP5bedXTB8is6fTv19U1GQRyQUKQGUTzyHACMF

// Account 0, root = m/84'/0'/0'
xpriv = zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE
xpub  = zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs

// Account 0, first receiving address = m/84'/0'/0'/0/0
privkey = KyZpNDKnfs94vbrwhJneDi77V6jF64PWPF8x5cdJb8ifgg2DUc9d
pubkey  = 0330d54fd0dd420a6e5f8d3624f5f3482cae350f79d5f0753bf5beef9c2d91af3c
address = bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu

// Account 0, second receiving address = m/84'/0'/0'/0/1
privkey = Kxpf5b8p3qX56DKEe5NqWbNUP9MnqoRFzZwHRtsFqhzuvUJsYZCy
pubkey  = 03e775fd51f0dfb8cd865d9ff1cca2a158cf651fe997fdc9fee9c1d3b5e995ea77
address = bc1qnjg0jd8228aq7egyzacy8cys3knf9xvrerkf9g

// Account 0, first change address = m/84'/0'/0'/1/0
privkey = KxuoxufJL5csa1Wieb2kp29VNdn92Us8CoaUG3aGtPtcF3AzeXvF
pubkey  = 03025324888e429ab8e3dbaf1f7802648b9cd01e9b418485c5fa4c1b9b5700e1a6
address = bc1q8c6fshw2dlwun7ekn9qwf37cu2rn755upcp6el

参照BIP

techmedia-think.hatenablog.com techmedia-think.hatenablog.com techmedia-think.hatenablog.com techmedia-think.hatenablog.com

ElementsでConfidential Transactionを作ってみる

techmedia-think.hatenablog.com

Elementsのセットアップとコインの移動ができたので↑、続いてElements上でConfidential Transactionを作ってみる。

Confidential Transactionについては↓

techmedia-think.hatenablog.com

以下のサイトを参考にConfidential Transactionを作ってみる。

Confidential Transactions — The Elements Project

Confidential Address

Elementsでアドレスを生成すると以下のようにBitcoinのアドレス形式とは異なっているのが分かる。

$ elements-cli getnewaddress
CTErPjNQRCAK6r3Nd3oARyrNhVRecmn6fuAQ7YJABN7KWQdML5uwPyLE7sLmY6wXVrCCnrCqytNYukyJ

CTで始まり長さもBitcoinアドレスより長い。CTで始まるのはベースアドレスの先頭にBlinding Keyから生成したconfidential_key(後述)含まれていることを意味する。

validateaddressでこのアドレスの詳細情報が確認できる。

$ elements-cli validateaddress CTErPjNQRCAK6r3Nd3oARyrNhVRecmn6fuAQ7YJABN7KWQdML5uwPyLE7sLmY6wXVrCCnrCqytNYukyJ
{
  "isvalid": true,
  "address": "CTErPjNQRCAK6r3Nd3oARyrNhVRecmn6fuAQ7YJABN7KWQdML5uwPyLE7sLmY6wXVrCCnrCqytNYukyJ",
  "scriptPubKey": "76a914aabe7c2cb93edaa3448aa9607ff95849f101d90888ac",
  "confidential_key": "02f7b3997676d9c9f78630b335a99c383f620a7d4630ad7c2d989838b153467683",
  "unconfidential": "2dpzZTGFr8fg9jfQ85jpYyMCU8JxTMJEugt",
  "ismine": true,
  "iswatchonly": false,
  "isscript": false,
  "pubkey": "02540dad951d44b352e30cef19a8a5f25622a19683a2cad4e8152ada19b2d27876",
  "iscompressed": true,
  "account": "",
  "timestamp": 1512977085,
  "hdkeypath": "m/0'/0'/11'",
  "hdmasterkeyid": "24f47e54071c4349a86103bccc26c0636c898506"
}

Bitcoinvalidateaddressの情報に加えて以下の項目が追加されている。

  • confidential_key:Blinding Keyの公開鍵(CTで使うPedersen commitmentcommitment = xG + aHxGの値)
  • unconfidential:Blinding Keyを含まないP2PKHアドレス

Confidential Addressは先頭2バイトがバージョンで、その後32バイトがconfidential_keyとなる。単純にBase58エンコードされてるだけなので、デコードして対象データをピックアップすれば簡単にconfidential_keyは抽出できる。(↓bitcoinrbを使って算出するサンプル)

まずアドレスをBase58デコードすると↓

require 'bitcoin'

confidential_addr = 'CTErPjNQRCAK6r3Nd3oARyrNhVRecmn6fuAQ7YJABN7KWQdML5uwPyLE7sLmY6wXVrCCnrCqytNYukyJ'
decoded = Bitcoin::Base58.decode(confidential_addr)
> "04eb02f7b3997676d9c9f78630b335a99c383f620a7d4630ad7c2d989838b153467683aabe7c2cb93edaa3448aa9607ff95849f101d908ef4201bd"

先頭の04ebはConfidential Addressのversion bytes。続く32バイトの02f7b3997676d9c9f78630b335a99c383f620a7d4630ad7c2d989838b153467683confidential_key。その後のaabe7c2cb93edaa3448aa9607ff95849f101d908は公開鍵のハッシュで、最後の4バイトef4201bdはこれらのチェックサムになっている。

この公開鍵ハッシュからP2PKHアドレスを生成すると↑のunconfidentialのアドレスが導出できる。

Bitcoin.encode_base58_address('aabe7c2cb93edaa3448aa9607ff95849f101d908', 'eb') # ebはregtestのP2PKHアドレスのversion byte
> "2dpzZTGFr8fg9jfQ85jpYyMCU8JxTMJEugt"

ちなみにunconfidentialのアドレスを指定して、validateaddressをすると、confidentialの項目にConfidential Addressが表示される。

$ elements-cli validateaddress 2dpzZTGFr8fg9jfQ85jpYyMCU8JxTMJEugt
{
  "isvalid": true,
  "address": "2dpzZTGFr8fg9jfQ85jpYyMCU8JxTMJEugt",
  "scriptPubKey": "76a914aabe7c2cb93edaa3448aa9607ff95849f101d90888ac",
  "confidential_key": "",
  "unconfidential": "2dpzZTGFr8fg9jfQ85jpYyMCU8JxTMJEugt",
  "ismine": true,
  "iswatchonly": false,
  "confidential": "CTErPjNQRCAK6r3Nd3oARyrNhVRecmn6fuAQ7YJABN7KWQdML5uwPyLE7sLmY6wXVrCCnrCqytNYukyJ",
  "isscript": false,
  "pubkey": "02540dad951d44b352e30cef19a8a5f25622a19683a2cad4e8152ada19b2d27876",
  "iscompressed": true,
  "account": "",
  "timestamp": 1512977085,
  "hdkeypath": "m/0'/0'/11'",
  "hdmasterkeyid": "24f47e54071c4349a86103bccc26c0636c898506"
}

Confidential Transactionを作りたい場合は、sendtoaddresssendfromsendmanycreaterawtransactionでこのConfidential Addressを指定する。そのため量を秘匿してコインを受け取りたい場合は、相手にConfidential Addressを教える。

Blinding Key

Confidential Transactionでは量の秘匿に使用しているBlinding Keyが分かれば秘匿された量を確認することができる。そのためコインの受信者が第三者機関に取引金額の監査を依頼する場合などはこのBlinding Keyを共有する。

Blinding KeyはdumpblindingkeyにConfidential Addressを渡せばエクスポートできる。

$ elements-cli dumpblindingkey CTErPjNQRCAK6r3Nd3oARyrNhVRecmn6fuAQ7YJABN7KWQdML5uwPyLE7sLmY6wXVrCCnrCqytNYukyJ
e059cbb981a09c273b34a81906efa8865fda42679113ca421a58ecb0ee89c140

Blinding Keyを受け取った第三者機関は、importblindingkeyでBlinding Keyをインポートすれば、秘匿されたトランザクションの量が見れるようになる。

$ elements-cli importblindingkey CTErPjNQRCAK6r3Nd3oARyrNhVRecmn6fuAQ7YJABN7KWQdML5uwPyLE7sLmY6wXVrCCnrCqytNYukyJ e059cbb981a09c273b34a81906efa8865fda42679113ca421a58ecb0ee89c140

このBlinding KeyからPedersen CommitmentのxGを生成すると↓

blinding_key = 'e059cbb981a09c273b34a81906efa8865fda42679113ca421a58ecb0ee89c140'
confidential_key = ECDSA::Format::PointOctetString.encode(G.generator.multiply_by_scalar(blinding_key.to_i(16)), compression: true).bth
> '02f7b3997676d9c9f78630b335a99c383f620a7d4630ad7c2d989838b153467683'

validateaddressした際に表示されたconfidential_key(公開鍵=楕円曲線上の点)であることが分かる。

つまり、コインの受信者が送信者にConfidential Addressを教えるということは、confidential_key= Blinding Keyから生成した楕円曲線上の点を教えていることになる。そしてその点を算出する秘密鍵(Blinding Key)はコインの受信者が保持したままで送信者もその値を知らない。

Confidential Transactionの作成

sendtoaddressでコインを別のアドレスに送ってみる。

$ elements-cli sendtoaddress CTEsctcppCktGP7ARmMpLj3jq6UkhPqEsMPra5Jq2g4teyjAcUMv8mZD3J4GiCb5YvBcsNT9orqbwrBv 0.005
36802e52f04b4cb2dc0ae69e05e0cd39ed6949db81deb3c006883948d4f7725d

生成されたトランザクションを確認してみると↓

$ elements-cli getrawtransaction 36802e52f04b4cb2dc0ae69e05e0cd39ed6949db81deb3c006883948d4f7725d 1
...
  "txid": "36802e52f04b4cb2dc0ae69e05e0cd39ed6949db81deb3c006883948d4f7725d",
  "hash": "f1aa768af41856d8e9adfbfd3fb377cff6343d53e7dd36eefa4fcde463213997",
  "withash": "bb617eab4d549f3c0ede13627e97deddd379459a1939762288fc64fbf1e0c96e",
  "size": 5744,
  "vsize": 1775,
  "version": 2,
  "locktime": 2,
  "vin": [
    {
      "txid": "d54755e5ea53083d2a096751f67c62e9e7d090b6b3ad8b157b4444fbaa9d74d2",
      "vout": 0,
      "scriptSig": {
        "asm": "3044022057546130be149c71dd04760fb0f96d9a484bf8a78c974badb00d87ef7e8a9fc002206d65a0f2f829b13ff75fd3c14c7ebdc72e4f736088239d5d9d5dfada574e4823[ALL] 0355d680cbab673077a6dddcae982810e0e236c2182910a6434c4b9db4fa63e3ca",
        "hex": "473044022057546130be149c71dd04760fb0f96d9a484bf8a78c974badb00d87ef7e8a9fc002206d65a0f2f829b13ff75fd3c14c7ebdc72e4f736088239d5d9d5dfada574e482301210355d680cbab673077a6dddcae982810e0e236c2182910a6434c4b9db4fa63e3ca"
      },
      "is_pegin": false,
      "scriptWitness": [
      ],
      "pegin_witness": [
      ],
      "sequence": 4294967294
    }
  ],
  "vout": [
    {
      "value-minimum": 0.00000001,
      "value-maximum": 42.94967296,
      "ct-exponent": 0,
      "ct-bits": 32,
      "amountcommitment": "08e6cb1c2118fa492df6782f84d496882ced45b4e759d7cafd507cc2211d217cef",
      "assetcommitment": "0b6f403c9f46739a0b262c393da46913abcd63fc63e3104f7a5b4de800e39c6bf4",
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 63fbcf117ec0a9c7fd5035fd084096d4fe7a89bc OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a91463fbcf117ec0a9c7fd5035fd084096d4fe7a89bc88ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "2diYQwVkH9yJK4Dtcw7Tcx3ZwZTzX8hzCyK"
        ]
      }
    }, 
    {
      "value-minimum": 0.00000001,
      "value-maximum": 42.94967296,
      "ct-exponent": 0,
      "ct-bits": 32,
      "amountcommitment": "084c55083b0b65ce88d68151b3a051736ac2903a3726106b3649a80c9a9e2abe26",
      "assetcommitment": "0bde7bfe359be84bd2603f803423639d0a5c33ceb36c79b629492a94bc3ffafb78",
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 dcfd7d0c5155c0de4d46ba3c25061684cf0185d7 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914dcfd7d0c5155c0de4d46ba3c25061684cf0185d788ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "2duaEiQggnyMz3MnXp6eV2QAYjwFoPiS4nv"
        ]
      }
    }, 
    {
      "value": 0.00035520,
      "asset": "d381261986457b55823c475adfaaad05268dcd85734f99acf7a96a4031d03bc2",
      "n": 2,
      "scriptPubKey": {
        "asm": "",
        "hex": "",
        "type": "fee"
      }
    }
  ],
  "blockhash": "1a046a2ea884024f0c9fea6489f6a74be59fe22c3e7d2e42c823b72f72c9cdfe",
...

UTXOの量がvalueでなく、"value-minimum": 0.00000001"value-maximum": 42.94967296と値の範囲が記載され、量の値代わりに量のコミットメントamountcommitmentが表示される。手数料も明示的にアウトプットに入っている。

また↑には記載していないがトランザクションhex値が非常に大きい。これはrange proofが含まれているからだろう。

amountcommitmentについてはPedersen Commitmentを計算すれば↓

amount = 500000 # satoshi

commitment = confidential_key + H.multiply_by_scalar(amount)

求められそうだけど、amountcommitmentと一致しない。通常楕円曲線の点をシリアライズすると圧縮形式では02 or 03で始まるんだけど、表示されているamountcommitment08で始まっている。このPedersen Commitmentのシリアライズ方法?とかどこに明記されてるんだろう?

↑ではsendtoaddressを使用したが以下のコマンドを組み合わせても同様のことはできる。

  • createrawtransaction
    Confidential Transactionを作る場合は送付先にConfidential Addressを指定する。また、インプットがブラインディングされている場合、インプット指定時にnValueでそのインプットのコインの量を明示的に指定する(コインの量はlistunspentで確認できる)。この時作られたトランザクションはまだブラインディングされてないので、戻り値のhexデータをデコードすると量はまだ確認できる。
  • blindrawtransaction
    createrawtransactionで作成したトランザクションのインプットもしくはアウトプットの量が秘匿されている場合、blindrawtransactionを実行する。1つのインプットが秘匿されている場合、少なくとも1つのアウトプットは秘匿されていなければならない(値が0の秘匿アウトプットを作成するのも可)。また全インプットが秘匿されていない場合、秘匿アウトプットの数は0か2以上でなければならない(ここでも値が0の秘匿アウトプットを作成するのも可)。
  • signrawtransaction
  • sendrawtransaction

制限事項

プロジェクトサイトに記載されているElementsの制限事項。

実装は指定された精度レベルのrange proofに応じて、各トランザクションアウトプットの所定の桁の数を隠すだけとなる。その後に0.0001 BTCのminimum confidential amountと、minimum amountの232倍のmaximum confidential amountが続く。

最小値より小さい桁数はオブザーバーに明らかになる。例えば最小値が0.0001 BTCの場合、123.456789 BTCのトランザクションアウトプットは?.????89 BTCと見える。このように漏れる金額はわずかなので、これ自体はそれほど重要ではないが、第三者が後続のトランザクションを同じ金額でリンクすることでコインを追跡できるようになることに注意する必要がある。全ての値を最小値に丸めることでこういった情報を公開するのを避けることができる。

最大値より大きいトランザクションアウトプットは、第三者に金額の大きさのオーダーを明かすことになる。例えば最大値が500k BTCの場合、その量以下のアウトプットは同じように見えるが、500k〜5M BTCのアウトプットは第三者に分かってしまう(ただその下の量はわからないので正確な量は分からないが)。こういった最大値を超える部分の値が明らかになるのは重要なプライバシーの漏洩になる可能性がある。そのため、最大値を超えるような高額のトランザクションは、量を分割して最大値以下にすることを強く推奨する。

↑のトランザクションだと、最小値と最大値はそれぞれ

  "value-minimum": 0.00000001,
  "value-maximum": 42.94967296

となっているので、最小値は1 satoshi(つまり最小値に関しては値が明らかになることはない)で、最大値は42.94967296 BTCが設定されているようだ(つまり42.94967296より多い部分の値については明らかになる)。

とりあえずElementsのRPCを利用してConfidential Transactionの作成、ブロードキャストはできたけど、実際にElementsと互換のあるCTを自前のライブラリで作ろうとすると、もう少し詳細な仕様が無いと辛いかな。あとできればrange proofの生成のロジックとか追ってみたい。

サイドチェーンにBitcoinをペグしてみる。

Confidential TransactionをサポートしているElementsで実際にCTを作ってみようと思い、まずElementsのセットアップしてサイドチェーンにコインをペグする。

github.com

Elementsのセットアップ

今回使用したElementsはelements-0.14.1ブランチのソース。(久しぶりに見たら0.14.1がアクティブブランチになってる。Alphaはもう使われてない?)

↓に独自サイドチェーン(elementsのregtest)のビルド手順が書いてあるので、これを参考にして環境をセットアップする。

Building A New Sidechain with Elements — The Elements Project

Elementsのビルド

$ git clone https://github.com/ElementsProject/elements.git
$ cd elements
$ ./autogen.sh 
$ ./configure
$ make -j3

makeが終わったらsrc直下にelementsdelements-clielements-txなどが生成されてる。

起動

基本的にはbitcoindと同じ。

$ elementsd -regtest -daemon

起動するとUbuntuの場合$HOME/.bitcoin/以下にelementsregtestというディレクトリができ、チェーンのデータはこのディレクトリ配下で管理される。

getinfoすると2100万 Bitcoinがあることが分かる。

$ elements-cli getinfo
{
  "version": 2140101,
  "protocolversion": 70015,
  "walletversion": 130000,
  "balance": {
    "bitcoin": 21000000.00000000
  },
  "blocks": 0,
  "timeoffset": 0,
  "connections": 0,
  "proxy": "",
  "difficulty": 1,
  "chain": "elementsregtest",
  "keypoololdest": 1512955437,
  "keypoolsize": 100,
  "paytxfee": 0.00000000,
  "relayfee": 0.00001000,
  "errors": ""
}

Elementsはサイドチェーンなので基本的に通貨発行は行わず、Bitcoinとペグする形でコインを入手する。ペグされたBitcoinと同量のコインがElements上でアンロックされる仕組み(Elements上のコインの名前もbitcoinなのは紛らわしいよね)。

サイドチェーンにコインをペグするのに、Bitcoin⇔Elements間のコインのペグの仕組みについて理解しておいた方が良い↓

techmedia-think.hatenablog.com

ちなみに初期状態でlistunspentすると↓のようなTXIDがb661d7fa55c27dc4dcd804e7640b23f92578a4b1ad0fbffbc2095eaec251ce34のUTXOが100個ほど出てくる。2,100万コインを100個のUTXOに分割して保持しているのが分かる。

{
  "txid": "b661d7fa55c27dc4dcd804e7640b23f92578a4b1ad0fbffbc2095eaec251ce34",
  "vout": 0,
  "scriptPubKey": "51",
  "amount": 210000.00000000,
  "asset": "d381261986457b55823c475adfaaad05268dcd85734f99acf7a96a4031d03bc2",
  "assetlabel": "bitcoin",
  "confirmations": 2,
  "spendable": true,
  "solvable": true,
  "blinder": "0000000000000000000000000000000000000000000000000000000000000000",
  "assetblinder": "0000000000000000000000000000000000000000000000000000000000000000"
}

設定ファイル

設定ファイル名はelements.confで(Bitcoin Coreと同様デフォルトでは作成されないので自分で作る)、Ubuntuだと$HOME/.bitcoinディレクトリ以下を探しにいく。

Elementsではメインチェーン(Bitcoin)上でコインがロックされたのを確認した上で、同量のコインをサイドチェーン(Elements)上でアンロックするが、その際メインチェーンで確かにコインがロックされているか検証するため、メインチェーンのノードに対してRPCアクセスをしている*1。そのため以下のようにelements.confにメインチェーンのノードのRPCアクセス情報を設定する必要がある。

rpcuser=bitcoinrpc(ElementsのRPCユーザー)
rpcpassword=password(ElementsのRPCパスワード)
rpcport=8339(ElementsのRPCポート)
daemon=1
discover=0
testnet=0
regtest=1
mainchainhost=127.0.0.1(BitcoinノードのIP)
mainchainrpcport=8338(BitcoinノードのRPCポート)
mainchainrpcuser=bitcoinrpc(BitcoinノードのRPCユーザー)
mainchainrpcpassword=password(BitcoinノードのIRPCパスワード)
validatepegin=1
txindex=1

Elementsにペグインする際のFederationスクリプトに使用する鍵の生成

Elementsのブロックは、予め決められたユーザー(公開鍵)を持つ人たち(block signer)の一定数が署名する(マルチシグ)ことで生成される。regtestなど独自チェーンを作る場合、まずこのブロックの署名に使用するスクリプトを生成する。

署名に使う新しいアドレスを生成し、

$ elements-cli getnewaddress
CTEuyLmLtg5x82wGhS9cDnAF9DUZcS1EgXTsgHMqJfNDqArjaGQVGsattpCE2CMrh6B1RRnhSUfQ6RGn

アドレスの公開鍵と秘密鍵を取得する。

$ elements-cli validateaddress CTEuyLmLtg5x82wGhS9cDnAF9DUZcS1EgXTsgHMqJfNDqArjaGQVGsattpCE2CMrh6B1RRnhSUfQ6RGn
{
  "isvalid": true,
  "address": "CTEuyLmLtg5x82wGhS9cDnAF9DUZcS1EgXTsgHMqJfNDqArjaGQVGsattpCE2CMrh6B1RRnhSUfQ6RGn",
  "scriptPubKey": "76a91440f6a609d0969b6cbef9557fa8cbedb012472c8088ac",
  "confidential_key": "037be078e2456585073c8f083b822b2a8d307f34144992436ad9e75df101cb3f8f",
  "unconfidential": "2dfMF7EtbPGTREZATPgePtcLXCGby6qPwPg",
  "ismine": true,
  "iswatchonly": false,
  "isscript": false,
  "pubkey": "03d4d908226f1a075022798611f02ee992271f7581c0f527757ccfb603c43fae28", ←これが公開鍵
  "iscompressed": true,
  "account": "",
  "timestamp": 1512955437,
  "hdkeypath": "m/0'/0'/2'",
  "hdmasterkeyid": "da51d8c6f773da39cfc48d90e1888ff25dcc4993"
}
$ elements-cli dumpprivkey CTEuyLmLtg5x82wGhS9cDnAF9DUZcS1EgXTsgHMqJfNDqArjaGQVGsattpCE2CMrh6B1RRnhSUfQ6RGn
cUgf4MyWeysP5iJMru4RbRpXgoCuSaz3X4beBa3qfqPwvGENity1

続いてブロックに署名するマルチシグスクリプトを作成する。↑の公開鍵使って、ここでは1-of-1のマルチシグを作成する↓

$ elements-cli createmultisig 1 \[\"03d4d908226f1a075022798611f02ee992271f7581c0f527757ccfb603c43fae28\"\]
{
  "address": "XHfz21yi9JkdtYBzYf7voeYn7et3tq4Khc",
  "redeemScript": "512103d4d908226f1a075022798611f02ee992271f7581c0f527757ccfb603c43fae2851ae"
}

このマルチシグスクリプトをサイドチェーンのブロック署名用に使用する。一旦Elementsを停止し、既存のチェーンデータをリセットしてマルチシグスクリプト(↑で生成したredeemScript)を指定してElementsを起動する。今回はペグしたコインをサイドチェーンに移動する際に使われる-fedpegscriptも同じスクリプトを指定しておく。

$  rm -Rf ~/.bitcoin/elementsregtest
$ elementsd -regtest -daemon -signblockscript=512103d4d908226f1a075022798611f02ee992271f7581c0f527757ccfb603c43fae2851ae  -fedpegscript=512103d4d908226f1a075022798611f02ee992271f7581c0f527757ccfb603c43fae2851ae

チェーンをリセットしてるので、このマルチシグに署名できるよう、↑で作成した秘密鍵をインポートする。

$ elements-cli importprivkey cUgf4MyWeysP5iJMru4RbRpXgoCuSaz3X4beBa3qfqPwvGENity1

ブロックの生成

getnewblockhexでまだマイニングされていない新しいブロックのデータを生成する。

$ elements-cli getnewblockhex
0000002041b97650a81cb6395a79e3109ac5df68c7dceb40a7b65de16baff69fe4c143a61afe4f13e39ed34497613ac34d48b42cfae8590ea386dea33a70f5a178b9b47e1a0a2e5a0100000025512103d4d908226f1a075022798611f02ee992271f7581c0f527757ccfb603c43fae2851ae00010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0201b34490609efeb1e9d19fa51275c76fd288aa7cc3f662c54c04b44aa242ef398e01000000000000000000016a01b34490609efeb1e9d19fa51275c76fd288aa7cc3f662c54c04b44aa242ef398e01000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000

signblockでブロックデータの署名を生成する。

$ elements-cli signblock 0000002041b97650a81cb6395a79e3109ac5df68c7dceb40a7b65de16baff69fe4c143a61afe4f13e39ed34497613ac34d48b42cfae8590ea386dea33a70f5a178b9b47e1a0a2e5a0100000025512103d4d908226f1a075022798611f02ee992271f7581c0f527757ccfb603c43fae2851ae00010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0201b34490609efeb1e9d19fa51275c76fd288aa7cc3f662c54c04b44aa242ef398e01000000000000000000016a01b34490609efeb1e9d19fa51275c76fd288aa7cc3f662c54c04b44aa242ef398e01000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000
00473045022100b347f546a144f153913045da6a6c707d7b4cff7f12b7967bb1597aff080b9de10220790648dfad6a5c2f7e9320a236bf4a35bc2182337de77e67b9d44aa6dc8da8ff

署名ができたらcombineblocksigsにブロックデータと署名データを引数で渡して、署名ブロックを入手する。今回は1-of-1なのでこれで良いけど、複数の署名者がいる場合は、各署名者がsignblockで署名を生成し、その署名をまとめてcombineblocksigsを実行する。

$ elements-cli combineblocksigs 0000002041b97650a81cb6395a79e3109ac5df68c7dceb40a7b65de16baff69fe4c143a61afe4f13e39ed34497613ac34d48b42cfae8590ea386dea33a70f5a178b9b47e1a0a2e5a0100000025512103d4d908226f1a075022798611f02ee992271f7581c0f527757ccfb603c43fae2851ae00010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0201b34490609efeb1e9d19fa51275c76fd288aa7cc3f662c54c04b44aa242ef398e01000000000000000000016a01b34490609efeb1e9d19fa51275c76fd288aa7cc3f662c54c04b44aa242ef398e01000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000 \[\"00473045022100b347f546a144f153913045da6a6c707d7b4cff7f12b7967bb1597aff080b9de10220790648dfad6a5c2f7e9320a236bf4a35bc2182337de77e67b9d44aa6dc8da8ff\"\]
{
  "hex": "0000002041b97650a81cb6395a79e3109ac5df68c7dceb40a7b65de16baff69fe4c143a61afe4f13e39ed34497613ac34d48b42cfae8590ea386dea33a70f5a178b9b47e1a0a2e5a0100000025512103d4d908226f1a075022798611f02ee992271f7581c0f527757ccfb603c43fae2851ae4900473045022100b347f546a144f153913045da6a6c707d7b4cff7f12b7967bb1597aff080b9de10220790648dfad6a5c2f7e9320a236bf4a35bc2182337de77e67b9d44aa6dc8da8ff010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0201b34490609efeb1e9d19fa51275c76fd288aa7cc3f662c54c04b44aa242ef398e01000000000000000000016a01b34490609efeb1e9d19fa51275c76fd288aa7cc3f662c54c04b44aa242ef398e01000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000",
  "complete": true
}

最後に生成した署名付きブロックをネットワークにサブミットする。

$ elements-cli submitblock 0000002041b97650a81cb6395a79e3109ac5df68c7dceb40a7b65de16baff69fe4c143a61afe4f13e39ed34497613ac34d48b42cfae8590ea386dea33a70f5a178b9b47e1a0a2e5a0100000025512103d4d908226f1a075022798611f02ee992271f7581c0f527757ccfb603c43fae2851ae4900473045022100b347f546a144f153913045da6a6c707d7b4cff7f12b7967bb1597aff080b9de10220790648dfad6a5c2f7e9320a236bf4a35bc2182337de77e67b9d44aa6dc8da8ff010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0201b34490609efeb1e9d19fa51275c76fd288aa7cc3f662c54c04b44aa242ef398e01000000000000000000016a01b34490609efeb1e9d19fa51275c76fd288aa7cc3f662c54c04b44aa242ef398e01000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000

サブミットしたらブロックが追加されてることが確認できる。

$ elements-cli getblockcount
1

生成したブロックを確認すると

elements-cli getblock 7a48bd4377102108abff8d2b58ae22d096e06242db82244e494ef5f1a09dfc54
{
  "hash": "7a48bd4377102108abff8d2b58ae22d096e06242db82244e494ef5f1a09dfc54",
  "confirmations": 1,
  "strippedsize": 371,
  "size": 412,
  "weight": 1525,
  "height": 1,
  "version": 536870912,
  "versionHex": "20000000",
  "merkleroot": "7eb4b978a1f5703aa3de86a30e59e8fa2cb4484dc33a619744d39ee3134ffe1a",
  "tx": [
    "7eb4b978a1f5703aa3de86a30e59e8fa2cb4484dc33a619744d39ee3134ffe1a"
  ],
  "time": 1512966682,
  "mediantime": 1512966682,
  "nonce": 1,
  "bits": "1 03d4d908226f1a075022798611f02ee992271f7581c0f527757ccfb603c43fae28 1 OP_CHECKMULTISIG",
  "difficulty": 1,
  "chainwork": "0000000000000000000000000000000000000000000000000000000000000002",
  "previousblockhash": "a643c1e49ff6af6be15db6a740ebdcc768dfc59a10e3795a39b61ca85076b941"
}

Bitcoinの場合だとbitsには難易度の閾値がセットされるが、Elementsのブロックヘッダでは、ブロックの署名者のマルチシグスクリプトがセットされている。

BitcoinからElementsにペグイン

Elementsのセットアップができたので、BitcoinのコインをElementsに移動してみる。

Bitcoin Coreもregtestで起動しておく(このノードのRPCのアクセス情報と↑のelements.confの内容が一致してること)。

ペグインするアドレスを生成

getpeginaddressでペグインの際に使用するアドレスを生成する。(getnewaddressなどと同様、生成したアドレスの秘密鍵はウォレットファイルに追加される。)

$ elements-cli getpeginaddress
{
  "mainchain_address": "2Muv3LwF8YxV4Jh6mpvGjN9d77AaH1CRfaw",
  "claim_script": "0014e8d28b573816ddfcf98578d7b28543e273f5a72a"
}

mainchain_addressはサイドチェーンにコインをペグする際に、ペグするコインのメインチェーン上での送付先アドレス。

このアドレスにメインチェーンでペグする分のコインを送る。

$ bitcoin-cli -regtest sendtoaddress "2Muv3LwF8YxV4Jh6mpvGjN9d77AaH1CRfaw" 1
d9017e5efc86d2e6e52a39f24edf977fc33e3d7e6eaa0d7a7e8e7599424daf81

ペグされたコインの入手

インチェーン上でペグするコインをロックしたら、サイドチェーン上でclaimpeginを実行しサイドチェーンのコインを入手する。claimpeginは以下の3つの引数を取る。

  • bitcoinTx
    mainchain_address宛にコインをデポジットしたBitcoinトランザクションのrawデータ。
  • txoutproof
    bitcoinTxのトランザクションがブロックに格納されていることの証明。データ自体はmerkleblockメッセージで使われているデータと同じで、そのトランザクションがブロックに含まれていることを示すマークルパスとマークルルートを計算するのに必要なハッシュ。これはメインチェーン上でgettxoutproofを実行すれば取得できる。
  • claim_script(オプション)
    getpeginaddressで生成した際のwitness programを指定。ウォレットに登録されていない場合にこのオプション使って指定する。

bitcoinTxhは↓

$ bitcoin-cli -regtest getrawtransaction d9017e5efc86d2e6e52a39f24edf977fc33e3d7e6eaa0d7a7e8e7599424daf81
02000000014578ddc14da3e19445b6e7b4c61d4af711d29e2703161aa9c11e4e6b0ea08843010000006b483045022100eea27e89c3cf2867393263bece040f34c03e0cddfa93a1a18c0d2e4322a37df7022074273c0ab3836affba53737c83673ca6c0d69bffdf722b4accfd7c0a9b2ea4e60121020bfcdbda850cd250c3995dfdb426dc40a9c8a5b378be2bf39f6b0642a783daf2feffffff02281d2418010000001976a914b56872c7b363bfb3f5af84d071ff282cf2abfe3988ac00e1f5050000000017a9141d4796c6e855ae00acecb0c20f65dd8bbeffb1ec87d1000000

txoutproofは↓

$ bitcoin-cli -regtest gettxoutproof \[\"d9017e5efc86d2e6e52a39f24edf977fc33e3d7e6eaa0d7a7e8e7599424daf81\"\]
03000030ffba1d575800bf37a1ee1962dee7e153c18bcfc93cd013e7c297d5363b36cc2d63d5c4a9fdc746b9d3f4f62995d611c34ee9740ff2b5193ce458fdac6d173800ec402e5affff7f200500000002000000027ce06590120cf8c2bef7726200f0fa655940cadcf62708d7dc9f8f2a417c890b81af4d4299758e7e7a0daa6e7e3d3ec37f97df4ef2392ae5e6d286fc5e7e01d90105

↑の2つを使ってサイドチェーンでclaimpeginを時刻する。

regtestの注意事項

regtestでclaimpeginを使用する際は以下の点に注意する。

  • elementsのノードの最新ブロックが1日以上古い場合、まだIBD中と判断されてエラーになるので、claimpegin実行時にブロックが一日以上古かったら↑に書いてるようにElementsのブロックを生成すること。
  • ロックされたトランザクションを確認するためbitcoindに対してRPCアクセスするが、ロックしたトランザクションがブロックに格納されてから10 confirmation経っていないとエラーになるので、Bitcoinのregtest側で事前に10ブロック作っておくこと。

※ txoutproofはメインチェーン上でペグしたトランザクションがブロックに格納されていることの保証にはなるが、それ単体ではブロックが実際にチェーン上に存在しているか、またそのブロックのConfirmation数がいくつかは分からないので、そういった確認をメインチェーンのノードにRPCして確認している。

$ elements-cli claimpegin 02000000014578ddc14da3e19445b6e7b4c61d4af711d29e2703161aa9c11e4e6b0ea08843010000006b483045022100eea27e89c3cf2867393263bece040f34c03e0cddfa93a1a18c0d2e4322a37df7022074273c0ab3836affba53737c83673ca6c0d69bffdf722b4accfd7c0a9b2ea4e60121020bfcdbda850cd250c3995dfdb426dc40a9c8a5b378be2bf39f6b0642a783daf2feffffff02281d2418010000001976a914b56872c7b363bfb3f5af84d071ff282cf2abfe3988ac00e1f5050000000017a9141d4796c6e855ae00acecb0c20f65dd8bbeffb1ec87d1000000 03000030ffba1d575800bf37a1ee1962dee7e153c18bcfc93cd013e7c297d5363b36cc2d63d5c4a9fdc746b9d3f4f62995d611c34ee9740ff2b5193ce458fdac6d173800ec402e5affff7f200500000002000000027ce06590120cf8c2bef7726200f0fa655940cadcf62708d7dc9f8f2a417c890b81af4d4299758e7e7a0daa6e7e3d3ec37f97df4ef2392ae5e6d286fc5e7e01d90105
27112a69cb01228939f993e93c4f94f764703eb3f3ee05aad7af17a7d9f7329c

正常に終わるとTXIDが返されるので、そのトランザクションを確認すると

$ elements-cli getrawtransaction 27112a69cb01228939f993e93c4f94f764703eb3f3ee05aad7af17a7d9f7329c 1
{
  "hex": "02000000010181af4d4299758e7e7a0daa6e7e3d3ec37f97df4ef2392ae5e6d286fc5e7e01d90100004000ffffffff0201c23bd031406aa9f7ac994f7385cd8d2605adaadf5a473c82557b4586192681d3010000000005f5c940001976a91495208a4cf9f3e6fdf06178baed1bebb8a874b6bf88ac01c23bd031406aa9f7ac994f7385cd8d2605adaadf5a473c82557b4586192681d30100000000000017c0000000000000000002473044022075282f574650e20c3a87d0d1f67d0bcd8f9319b26d244eb254c0aa5bc0284e8002205bddfd4e2f5e278de5f473804a1d061ed6f9bdbcb65fec9b20402879c5a998090121025c36c65910268ee06421053cb9bab1c849c4bdd467d6e77a89d33ff213adc3ca060800e1f5050000000020c23bd031406aa9f7ac994f7385cd8d2605adaadf5a473c82557b4586192681d32006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f160014e8d28b573816ddfcf98578d7b28543e273f5a72ae002000000014578ddc14da3e19445b6e7b4c61d4af711d29e2703161aa9c11e4e6b0ea08843010000006b483045022100eea27e89c3cf2867393263bece040f34c03e0cddfa93a1a18c0d2e4322a37df7022074273c0ab3836affba53737c83673ca6c0d69bffdf722b4accfd7c0a9b2ea4e60121020bfcdbda850cd250c3995dfdb426dc40a9c8a5b378be2bf39f6b0642a783daf2feffffff02281d2418010000001976a914b56872c7b363bfb3f5af84d071ff282cf2abfe3988ac00e1f5050000000017a9141d4796c6e855ae00acecb0c20f65dd8bbeffb1ec87d10000009703000030ffba1d575800bf37a1ee1962dee7e153c18bcfc93cd013e7c297d5363b36cc2d63d5c4a9fdc746b9d3f4f62995d611c34ee9740ff2b5193ce458fdac6d173800ec402e5affff7f200500000002000000027ce06590120cf8c2bef7726200f0fa655940cadcf62708d7dc9f8f2a417c890b81af4d4299758e7e7a0daa6e7e3d3ec37f97df4ef2392ae5e6d286fc5e7e01d9010500000000",
  "txid": "27112a69cb01228939f993e93c4f94f764703eb3f3ee05aad7af17a7d9f7329c",
  "hash": "440e7c28917bbb9c3145b83c1bc2307b4a4d4b9b54e6a3d0dd0dcb77630054d8",
  "withash": "6767fc94b1e1df216d048d77c739158afa3505a7b3877ad1bb28d12e9164f893",
  "size": 754,
  "vsize": 313,
  "version": 2,
  "locktime": 0,
  "vin": [
    {
      "txid": "d9017e5efc86d2e6e52a39f24edf977fc33e3d7e6eaa0d7a7e8e7599424daf81",
      "vout": 1,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "is_pegin": true,
      "scriptWitness": [
        "3044022075282f574650e20c3a87d0d1f67d0bcd8f9319b26d244eb254c0aa5bc0284e8002205bddfd4e2f5e278de5f473804a1d061ed6f9bdbcb65fec9b20402879c5a9980901", 
        "025c36c65910268ee06421053cb9bab1c849c4bdd467d6e77a89d33ff213adc3ca"
      ],
      "pegin_witness": [
        "00e1f50500000000", 
        "c23bd031406aa9f7ac994f7385cd8d2605adaadf5a473c82557b4586192681d3", 
        "06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f", 
        "0014e8d28b573816ddfcf98578d7b28543e273f5a72a", 
        "02000000014578ddc14da3e19445b6e7b4c61d4af711d29e2703161aa9c11e4e6b0ea08843010000006b483045022100eea27e89c3cf2867393263bece040f34c03e0cddfa93a1a18c0d2e4322a37df7022074273c0ab3836affba53737c83673ca6c0d69bffdf722b4accfd7c0a9b2ea4e60121020bfcdbda850cd250c3995dfdb426dc40a9c8a5b378be2bf39f6b0642a783daf2feffffff02281d2418010000001976a914b56872c7b363bfb3f5af84d071ff282cf2abfe3988ac00e1f5050000000017a9141d4796c6e855ae00acecb0c20f65dd8bbeffb1ec87d1000000", 
        "03000030ffba1d575800bf37a1ee1962dee7e153c18bcfc93cd013e7c297d5363b36cc2d63d5c4a9fdc746b9d3f4f62995d611c34ee9740ff2b5193ce458fdac6d173800ec402e5affff7f200500000002000000027ce06590120cf8c2bef7726200f0fa655940cadcf62708d7dc9f8f2a417c890b81af4d4299758e7e7a0daa6e7e3d3ec37f97df4ef2392ae5e6d286fc5e7e01d90105"
      ],
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.99993920,
      "asset": "d381261986457b55823c475adfaaad05268dcd85734f99acf7a96a4031d03bc2",
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 95208a4cf9f3e6fdf06178baed1bebb8a874b6bf OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a91495208a4cf9f3e6fdf06178baed1bebb8a874b6bf88ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "2do2G436PpVMqGxpzBoFJhTQncfKJbGU3YG"
        ]
      }
    }, 
    {
      "value": 0.00006080,
      "asset": "d381261986457b55823c475adfaaad05268dcd85734f99acf7a96a4031d03bc2",
      "n": 1,
      "scriptPubKey": {
        "asm": "",
        "hex": "",
        "type": "fee"
      }
    }
  ]
}
インプットについて

pegin_witnessの部分がペグインの証明で、上から順に以下の項目で構成されている。

アウトプットについて
  • ペグした1 BTCがP2PKH宛に0.99993920コイン送られ、手数料が差し引かれているのが分かる。またこのP2PKHのアドレスはキープールから新しい鍵を取得して割り当てているみたい。
  • 手数料の方は、feeとあるだけでscriptPubKeyスクリプトは空。
  • assetはコインの識別子。メインチェーンのコインとペグするコインの他に任意のアセットが発行できる(Confidentail Assets)ためその識別に使うものと思われる。

あとはまたブロック署名者の鍵を使って新しいブロックを作れば、↑のトランザクションもブロックに入る。listunspentすれば↑のUTXOも利用可能になっており、このコインを使ってサイドチェーンでコインの送付ができるようになる。

ひとまずCT作るための下準備ができたので次は実際にCTを作ってみよう。

*1:もともとのサイドチェーンのホワイトペーパーにあるようなSPV Proofの仕組みがあればこのRPCアクセスはおそらく不要になるが、そのためにはBitcoinに機能追加しないといけないので現状こういう形をとっていると思われる。

TREZOR導入してみた

TREZOR本体に付属のUSBケーブル使ってPCに接続する。

繋いだらブラウザ開いて、trezor.io/start にアクセス。

ページ下部で、TRZORのセキュリティニュースなどを配信してるみたいなので、メールアドレスを登録。

続いてTREZORウォレットのセットアップ。「Install Extension」を選択してChrome Extensionをインストールする。インストールを実行するとTREZORのファームウェアのアップデートが始まる。

f:id:techmedia-think:20171201214839p:plain

続いてTREZOR側でアップデートが成功した旨メッセージが出るので、PCからTREZOR外して再度接続する。そうするとPCのブラウザでセットアップが始まる↓

f:id:techmedia-think:20171201214901p:plain

バイス名を入力すると、続いてPINの設定を求められる↓

f:id:techmedia-think:20171201215226p:plain:w300

PC上に表示されているマスと同じ形のマスがTREZOR上に表示されそこには1〜9の数字が表示されているので、PINに設定する数値をTREZOR上のマスの数値を見ながら、PC上のマスを選択して設定する。

PINの設定が終わるとHDウォレットのマスタードシードの元になる24個のワードリストがTREZOR上に表示されるので、各ワードをメモっていく。24個のワードリストが表示されると、確認用にもういちどワードリストが表示される。それが終わればセットアップは完了。

受信用のタブを開いてみると受信用のアドレスが表示されているが、そのアドレスの導出パスはm/49'/0'/0'/0/0と記載されていて、SegwitのP2WPKHをP2SHでネストした3から始まるP2SHアドレスになってる。

f:id:techmedia-think:20171201230743p:plain

BIP-49のアドレス導出の仕様は↓

techmedia-think.hatenablog.com

折角なのでbitcoinrbで、↑のアドレスを導出してみた↓

require 'bitcoin'

Bitcoin.chain_params = :mainnet

words = %w(↑のTREZORの24個のワードリスト).map(&:downcase)

m = Bitcoin::Mnemonic.new('english')

# シードを生成
seed = m.to_seed(words)

# マスターキーを生成
master_key = Bitcoin::ExtKey.generate_master(seed)

# m/49'/0'/0'/0/0 のパスの公開鍵を導出
pubkey = master_key.derive(2**31 + 49).derive(2**31).derive(2**31).derive(0).derive(0).pub

# 公開鍵からP2WPKHのスクリプト作ってそれをネストしたP2SHのアドレスを生成
bip49adrr = Bitcoin::Script.to_p2wpkh(Bitcoin.hash160(pubkey)).to_p2sh.to_addr

↑はアドレスの導出だけど、秘密鍵も導出できてるのでそれ使えばトランザクションの署名も可能。ただ秘密鍵導出してもプログラムで署名するとあまりTREZOR使ってる意味ないので、↓みたいに拡張公開鍵を導出して持っておくとそこから、受信用のアドレスを随時導出していくことは可能。

# m/49'/0'/0'/0 の拡張公開鍵
ext_pubkey = master_key.derive(2**31 + 49).derive(2**31).derive(2**31).derive(0).ext_pubkey

# 拡張公開鍵があれば、
xpub = ext_pubkey.to_base58
-> 'xpub...'

# 拡張公開鍵があればそこから子公開鍵を導出
ext_pubkey = Bitcoin::ExtPubkey.from_base58(xpub)
ext_pubkey.derive(1)
ext_pubkey.derive(2)

任意のデータに対する署名検証を行うOP_DATASIGVERIFY(BUIP-78)

OP_GROUPに加えてBitcoin Cashの中期開発計画に含まれているのが、Bitcoinトランザクションの署名検証の際に使われるECDSAと同じアルゴリズムを使って、任意のデータの署名を検証するOP_DATASIGVERIFYという新しいopcode。

https://github.com/BitcoinUnlimited/BUIP/blob/master/078.mediawiki

通常BitcoinスクリプトではOP_CHECKSIGを使って署名の検証をするけど、この時検証に使われるデータはあくまでトランザクションのデータ。この署名検証においてトランザクションに限らず任意のデータに対して署名検証できるようにするスクリプトOP_DATASIGVERIFYというわけだ。

任意のデータについて署名検証できると何が嬉しいの?という話だが、外部の情報をトランザクションに持ち込むオラクルを想定しているようだ。

相変わらずBUIP単体ではプロトコルの内容が分からないので、Andrew Stoneのブログ記事を読もう↓

medium.com

Bitcoinというのはプログラマブルなお金なので、シンプルなプログラマブルマネーということで考えると以下のようなifを使った条件が考えられる。

if (condition) then pay A else pay B

この時重要になるのが、条件を評価するところ、特に外部データをインプットして条件評価をする部分だ。

基本的にBitcoinもEthereumもスクリプトサンドボックス環境で動作するようになっているため、スクリプトを評価する際にスクリプトが外部API叩いて値を取得するといったことはできない。まぁAPIを叩いたところでそれが正しいデータという保証は無いし、中間者攻撃のようなリスクも考えられる。

そのため外部データをブロックチェーンに取り込む場合、オラクルと呼ばれる信頼できるエンティティが登場する。オラクルは現実世界のデータをBitcoinスクリプトで利用可能な形式に変換して提供する存在だ。もちろんオラクルが嘘を付けば破綻するのでオラクルへの信頼が必要になる。極力このオラクルへの信頼を取り除いていく仕組みやインセンティブ設計がいろいろと研究されており、Discreet Log Contractsなんかもそういった点が設計されている↓

techmedia-think.hatenablog.com

ちなみにこのBUIP自体ではオラクルへの信頼の問題を解消する方法については特に触れてはいない。

Andrew Stoneのブログでは株価などのデータを提供するオラクルサービスを仮定し、単純なif文を使ってバイナリオプションがサンプルのユースケースとして挙げて、OP_DATASIGVERIFYの使い方を説明している。

オラクルのデータ提供方法

OP_RETURNの利用

データを提供するオラクルは、OP_RETURNを使ってデータをブロックチェーンに記録できるが、他のスクリプトがそのデータを参照する方法は無い。そこで新しいopcodedata = OP_GET_DATA(address)を追加する。このopcodeはaddressによって署名されたブロックチェーンの履歴を調べ、そのOP_RETURNのデータをスタックにプッシュする。そのアドレスで署名されているトランザクションが無い場合、opcodeは失敗して終了する。そのUTXOは現在使用不可能となる。まだオラクルが嘘をつくという問題はあるが、オラクルだけがそのアドレスのお金を使える(そのアドレスで署名できる)ため、それが提供元(オラクルよる)データであるという真正性は確認できる。

この方法の問題点は、オラクルがたくさんのトランザクションで情報をブロックチェーンに記録しても、それを使用するユーザーがおらず未使用に終わる可能性がある点だ。

OP_DATASIGVERIFYの利用

より現実的なオプションは、オラクルがバイナリオプションを使用するスクリプトの一部として使用できる署名付きにステートメントを個別に公開する方法だ。しかし、これはスクリプトがそのステートメントの真正性を検証しなければならないことを意味する。Bitcoinは簡単に署名を検証できるが、CHECKSIGopcodeはトランザクションデータへの署名のみを検証し、任意のデータの署名は検証できない。そこで

data = OP_DATASIGVERIFY(signature, data, pubkeyhash)

というopcodeを追加する必要がある。

このopcodeはトランザクションではなくスタック上のデータに対してOP_CHECKSIGVERIFYを行う。具体的には↓

  1. スタックからトップ3アイテム(公開鍵ハッシュ、署名、任意のデータ)をポップする。
  2. 署名が任意のデータに対する有効な署名か検証する。
  3. 署名の公開鍵のハッシュが公開鍵ハッシュと一致するか検証する。

この検証のいずれかで失敗するとトランザクションの検証は失敗する。検証が終わるとデータはスタックにプッシュバックされ残りのスクリプトで使われる。

オラクルが署名したデータが、1日の株価の中央値だったとする(証券コードと日付も含めて、データを一意にする)。このメタデータの検証をブロックチェーン上で行う場合、データを証券名、日付、金額の3つのパートに分け独立して評価されるよう、文字列を操作するopcodeを再度有効化する必要がある。

バイナリオプションのスクリプト構成例

とりあえずデータの一意性(証券名とか日付とか)を無視してバイナリオプションスクリプトを作成すると、バイナリオプションに勝ったユーザーが資金を入手する際のscriptSigは次のようなスタックを構成する。

オラクルの署名
オラクルのデータ
勝者の公開鍵 (標準のP2PKHの場合)
勝者のトランザクションの署名 (標準のP2PKHの場合)

参照されるscriptPubkeyは以下のような構成になる。

# オラクルの公開鍵をスタックにプッシュ
OP_PUSHDATA(オラクルの公開鍵ハッシュ)

# 上記のスクリプトのデータと署名を検証
OP_DATASIGVERIFY

# 検証が成功したら、オラクルのデータはスタック上に残り、バイナリオプションのストライクプライスをスタックにプッシュ
OP_PUSHDATA(バイナリオプションのストライクプライス)

# プッシュしたストライクプライスとオラクルのデータを比較
OP_LESSTHAN
# データの比較結果によって、参加者のいずれかへの支払いを許可する
OP_IF
  # 通常のP2PKHトランザクションの始まり
  OP_DUP  # 勝者の公開鍵をコピーする
  OP_HASH160 # 公開鍵をアドレスに変換
  OP_DATA(参加者Aのアドレス)
OP_ELSE
  OP_DUP
  OP_HASH160
  OP_DATA(参加者Bのアドレス)
OP_ENDIF
# 通常のP2PKHトランザクション終わり
OP_EQUALVERIFY # アドレスをハッシュされた公開鍵と比較
OP_CHECKSIGVERIFY # トランザクションと署名が一致するか検証

↑は単純な条件分岐だけど、MASTなんかと組み合わせるとより複雑な条件構成を持ち秘匿性のあるスクリプトにもできそう。

所感

  • 任意のデータについての署名検証ができると確かにコントラクトの応用は広がりそう。そういうユースケースが増えるとオラクルへの信頼問題の研究も進むかもね。
  • でもOP_GET_DATAブロックチェーン上のデータ検索してOP_RETURN探すとか流石に無謀だろう…。
  • ElementsやScaling BitcoinBitcoin Script 2.0なんかでも言われているけど、現在無効化されている文字列やビット列を操作するopcodeについては再有効化を求める声が多い。