Develop with pleasure!

福岡でCloudとかBlockchainとか。

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については再有効化を求める声が多い。

Bitcoin Cash上でネイティブにカラードコインをサポートするOP_GROUP

Bitcoin CashブロックチェーンでBCH以外にネイティブにカラードコインをサポートするためにOP_GROUPという新しいopcodeが提案されている↓

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

OP_GROUPとは?

OP_GROUPBitcoinスクリプト言語に追加する単一のopcodeで、ブロックチェーン上でトークン(カラードコイン)を効果的に表現する。OP_GROUPは既存のカラードコインの提案および実装とは異なり、通常のトランザクションの検証の一環でマイナーがカラードコインを検証する。これはビットコイン(BCH)と同様、無効なカラードコインのトランザクションブロックチェーンに現れないことを意味する(他のほとんどのカラードコインの提案はカラードコインのプロトコルとして無効なトランザクションブロックチェーン上に現れるため全てのクライアントがカラードコインのtxを検証する)。さらにこのOP_GROUPの提案では、全てのUTXOにカラーを明示する。これらの機能を一緒に追加することで以下が可能になる。

  1. ユーザーが誤ってカラードコインを通常のビットコインとして使用することがなくなる。
  2. SPVウォレットはビットコインと同じ容易さとセキュリティモデルでカラーリングされたコインを処理できる。

OP_GROUPの初期バージョンでは、他のカラードコインがサポートしている高度な機能の一部は含まれていないが、(SPVウォレットで有効など)カラードコインに必要な主要な機能を含んでいると思われる。この機能は他のカラードコインの提案にはない。

というのがBUIPに書かれてる内容だが、これだけだとどうやって実現するのか全く分からないので、リンクされている↓の記事を見てみる。(本筋とは関係ないけど、プロトコルの内容はちゃんとBUIP内に書こうよ。。)

medium.com https://www.yours.org/content/colored-coins-in-bitcoin-cash-b26804e05964/

OP_GROUPの挙動

スクリプト内にOP_GROUPが現れると、スタックの一番上のデータがグループ識別子として使われ、インプット/アウトプットの値がそのグループに関連付けられた整数に加算/減算される。この整数はgroup balanceと呼ばれる。OP_GROUPが無い場合、そのインプット/アウトプットはデフォルトのbitcoin cashグループとして扱われる。トランザクションの処理の最後に、bitcoin cashグループを除く各グループのバランスは0にならなければならない(bitcoin cashグループのインプットは手数料分アウトプットより多いためバランスは0にはならない)。言い換えると、全カラードコイングループ内で、satoshiのインプットとアウトプットのバランスは一致する。

通常のP2PKHのスクリプトは以下のような形式だが

OP_DUP OP_HASH160 <公開鍵のハッシュ> OP_EQUALVERIFY OP_CHECKSIG

カラーリングされたアウトプットの公開鍵ハッシュへの支払スクリプトは以下のような形式になる。

<グループ識別子> OP_GROUP OP_DROP OP_DUP OP_HASH160 <公開鍵のハッシュ> OP_EQUALVERIFY OP_CHECKSIG

OP_DUP以降はP2PKHと同じで、先頭にグループ識別子とOP_GROUPOP_DROPが付加されている。グループ識別子がscriptPubkey内にあるため、SPVウォレットでもUTXOがどのグループなのかを識別できるという仕組みだ。OP_GROUP命令はスタックの頂上からグループ識別子をポップしないので、その後のOP_DROPが必要になる。これは不便だが、アップグレードされていないクライアントはOP_GROUPをno-opとして解釈するため、OP_GROUPを理解しないクライアントでもトランザクションを有効と判断するようになる。このためこのスクリプトの変更でハードフォークは必要ない。

また今のところグループ識別子のデータフォーマットはまだ定義されてないみたい。

カラードコイントークンの作成と破壊

カラーリングされたコインを持つトランザクションのインプットとアウトプットのバランスは0になるということだったが、2つだけ例外がある。カラードコインのトークンを発行するmintと破壊するburnの場合だ。

mint トランザクション

以下のようなscriptPubkeyを持つインプットと

OP_DUP OP_HASH160 <公開鍵のハッシュ> OP_EQUALVERIFY OP_CHECKSIG

以下のscriptPubkeyを持つアウトプット

<グループ識別子> OP_GROUP OP_DROP OP_DUP OP_HASH160 <公開鍵のハッシュ> OP_EQUALVERIFY OP_CHECKSIG

が含まれるトランザクションを考える。インプットにはOP_GROUPがないため、通常ではグループのバランスが取れておらずトランザクションの検証に失敗するが、mintトランザクションであれば例外となる。アウトプット内のOP_GROUPのグループ識別子がインプットのデータ要素と一致する場合、mintトランザクションであると認識され、このインプットの値はアウトプット内のグループ識別子のグループの値として扱われる。

インプットのデータ要素というのはまだプロトコルで正式に定められている訳ではないようだが、アドレスやアドレスの元となる公開鍵などを利用する模様。公開鍵やそこから派生したアドレスを利用することで、その公開鍵に対応する秘密鍵を持つユーザーのみが同じ識別子のコインを作り出すことができる。これは既存のカラードコインのプロトコルでもよく採用されているアプローチだ。

最初のmintトランザクションはオプションでOP_RETURNを含めることができ、これには人間が識別できるグループの短い名前やグループの詳細について説明したドキュメント/コントラクトのハッシュが記録されている。

ちなみに↑のインプット1つカラーリングされたアウトプット1つのmintトランザクションだと手数料が設定できないので、実際には手数料分のインプットが追加されることになる。

↑のルールの場合、発行者は同じ公開鍵を使えば同じグループ識別子のコインを何度でも追加発行できる。そのため追加発行を許可しないようなコインの発行をどう制御するかは検討が必要。

burn トランザクション

burnトランザクションはカラーリングされたコインを通常のBitcoin Cashに戻す際に使用するトランザクション

トランザクションのインプットは↓のようなカラーリングされたscriptPubkeyのUTXOを参照していて

<グループ識別子> OP_GROUP OP_DROP OP_DUP OP_HASH160 <公開鍵のハッシュ> OP_EQUALVERIFY OP_CHECKSIG

アウトプットは以下のようにカラーリングされていないscriptPubkey

OP_DUP OP_HASH160 <公開鍵のハッシュ> OP_EQUALVERIFY OP_CHECKSIG

全アウトプットにOP_GROUPが含まれていない場合、burnトランザクションとみなされる。

グループを識別しやすくするためのルール

このアプローチでは、OP_GROUPが複雑なスクリプト内にある場合、トランザクションの検証時にどのグループのコインに属するか決定することができないかもしれないという問題があり、このためスクリプトのスタンダードルールとして以下の制約が設けようとしているみたい。

  • スクリプト内にOP_GROUPは1つのみ
  • OP_GROUPは条件付きのロジック(OP_IF、OP_ELSEとか)の外側であること
  • OP_GROUPの直前にグループ識別子がプッシュされていること

カラードコインの量

↑のルールでグループ識別子によってカラードコインを識別する仕組みが提供される。識別は可能だがカラーリングされたコインの量はそのままアウトプットのコインの量(satoshiの量)を使ってる。2ndレイヤーで実装するカラードコインの場合、カラーリングされたコインには専用の量があり、UTXOとして認識するために各アウトプットに少額のBitcoinが乗っているが、OP_GROUPの場合はBitcoinの量とカラードコインの量を分離してはいないのが特徴的でもある。ただいずれもカラーリングされている間はBitcoinとしては使えないという点は同じだ。

所感

  • インプットとアウトプットの各グループのコイン量のバランスのチェックは、各スクリプト単体の評価時には結果が分からないので、全インプット/アウトプットを考慮して計算しないといけない。つまりスクリプトインタプリタ単位では検証できないわけで、そういった評価の類をスクリプトのopcodeとして導入していいものか?本来スクリプトのランタイムはscriptSig + scriptPubkeyを組み合わせて実行して結果が出るもの。Covenantsの仕組みとかでその中でトランザクションデータをスタックに入れて評価するような拡張の提案もあるけど、OP_GROUPトランザクションの他のインプットも含めた上で評価する必要があり、検証のスコープが複数のインプットのスクリプトにまたがることになるけど、いいのか?。
  • 最初のmintトランザクションにはOP_RETURNメタデータを付与できると記載されているけど、これは最初のmintトランザクションかどうかをチェックしないといけないという意味になると考えると、どのトランザクションが最初の発行トランザクションであるか管理しておかなければならないということになる?
  • インプット/アウトプットでグループのコインのバランスがゼロになる必要があることから、おそらくP2SHは使えないと思われる。P2SHの場合、そのredeem scriptが明かされるのは、そのスクリプトを使用するトランザクションのscriptSigなので、アウトプットがP2SHだとそのredeem script内にOP_GROUPがあることをトランザクションの検証時に確認する術がない。
  • Bitcoinではネイティブにこういったトークンをサポートしてなくて、やる場合は2ndレイヤーで実装してたのもあり1stレイヤーのコンセンサスで保護されるわけではない。そのためトークンプラットフォームとしてはEthereumが人気な訳だけど、Cashが対応するとスクリプトの堅牢性という意味も含めてBitcoinベースのトークンプラットフォームも進化していくかもね。
  • ちなみにBTCでは、コア開発者の1人であるJohnson Lauがソフトフォークでカラードコインをネイティブに対応する提案を書いているが↓、実際に導入されることは無いだろうな。 btcnews.jp