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の生成のロジックとか追ってみたい。