Develop with pleasure!

福岡でCloudとかBlockchainとか。

Elementsで実装されているOP_CHECKSIGFROMSTACKを使ったCovenants

Scaling Bitcoinのセッションで提案されていた、Bitcoin秘密鍵が例え盗まれても、Bitcoin自体が盗まれないようにするCovenantsを使った金庫についてブログを書いた↓けど、

techmedia-think.hatenablog.com

このアプローチとは別に、Blockstreamが提供するサイドチェーンElements Alphaで提供されているOP_CHECKSIGFROMSTACKというopcodeを使ったCovenantsの例も紹介されてたので見てみる↓

https://blockstream.com/2016/11/02/covenants-in-elements-alpha.htmlblockstream.com

Bitcoinスクリプトシステムでは、スクリプトが直接トランザクションデータにアクセスすることはできず、OP_CHECKSIGOP_CHECKSIGVERIFYといった操作を介して間接的にアクセスする。これらのopcodeは公開鍵と署名を入力にとる。opcodeが実行されると、トランザクションデータのダブルSHA-256ハッシュを計算し、そのハッシュに対し署名データと公開鍵を使って署名の検証を行う。

Elements AlphaもBitcoinと同様スクリプト言語が直接トランザクションデータにアクセスすることはできないが、Elements Alphaには新しく追加されたOP_CHECKSIGFROMSTACKOP_CHECKSIGFROMSTACKVERIFYというopcodeがある。このopcodeは楕円曲線の公開鍵、メッセージ、署名の3つの入力を取り、実行するとメッセージのSHA-256ハッシュを生成し、デジタル署名と公開鍵を使ってハッシュされたメッセージデータのデジタル署名の検証をする。

Elements Alphaでは、このOP_CHECKSIGFROMSTACKOP_CHECKSIGを組み合わせて使うことでCovenantsを実現している。OP_CHECKSIGの検証の際に使った公開鍵とデジタル署名をそのまま、OP_CHECKSIGFROMSTACKの検証に使用するようscriptPubkeyを組むのがこのトリックの正体になる。どういうことかというと、全く同じ公開鍵とデジタル署名を使ってOP_CHECKSIGOP_CHECKSIGFROMSTACKの検証が両方成功するということは、OP_CHECKSIGの検証時に計算したそのトランザクションデータと同じデータがOP_CHECKSIGFROMSTACKのメッセージとして渡されていることを意味する。

具体的に↑のブログに記載されている例を見ながらCovenantsを構成するスクリプトを見ていく。

Covenantsの構成

以下のスクリプトを構成した場合

  1. OP_OVER OP_SHA256 < pubKey >
  2. 2 OP_PICK 1 OP_CAT OP_OVER
  3. OP_CHECKSIGVERIFY
  4. OP_CHECKSIGFROMSTACKVERIFY

スタックに以下の2つのアイテムがあると仮定して、

<signature>
<sigTransactionData>

OP_OVERはスタックの上から2つめのアイテムをコピーしてスタックの一番上にプッシュするopcode。 最初のステップが実行されると、< sigTransactionData >のSHA-256ハッシュと公開鍵がスタックにプッシュされて、スタックは以下のようになる。

<pubKey>
<sha256(sigTransactionData)>
<signature>
<sigTransactionData>

OP_PICKははスタック内の上からn番目のアイテムをコピーしてスタックの一番上にプッシュするopcode。OP_CATは2つの文字列を結合するopcode(ただしBitcoinプロトコルでは無効になっている)。

2番目のステップが実行されると、OP_PICKで< signature >がコピーされスタックにプッシュされ、それにOP_CATで0x01が付与される。その後スタックの2番目となった公開鍵がコピーされスタックの一番上にプッシュされ、スタックは以下のようになる。
署名に付与された0x01はSIGHASH_ALLで、署名されたトランザクションデータには全ての入力と出力が含まれていることを意味する。

<pubKey>
<signature;SIGHASH_ALL>
<pubKey>
<sha256(sigTransactionData)>
<signature>
<sigTransactionData>

3番目のステップのOP_CHECKSIGVERIFYにより、スタックの上から2つの公開鍵と署名のデータが正しい秘密鍵で署名された署名であるか検証する。検証が成功するとスタックは以下のようになる。

<pubKey>
<sha256(sigTransactionData)>
<signature>
<sigTransactionData>

4案目のステップのOP_CHECKSIGFROMSTACKVERIFYは、スタックから< pubkey >と< sha256(sigTransactionData) >、< signature >を取り出し、< sha256(sigTransactionData) >に対してSHA-256ハッシュを生成する。そうやって出来たダブルSHA-256したデータと< signature >について< pubkey >で署名の検証を行う。ここで使っている公開鍵と署名は、3つめのステップの署名検証で使った公開鍵と署名と同一のデータなので、OP_CHECKSIGFROMSTACKVERIFYは< sigTransactionData >が3つめのステップのOP_CHECKSIGVERIFYで検証されたメッセージと同じ場合のみ成功する。

<sigTransactionData>

この時点でスタック上の< sigTransactionData >は、署名されたトランザクションデータの正確なコピーであることが保証されている。トランザクションにフルアクセスすることで、追加のスクリプト操作でCovenantsを強制することができる。出力の数を制限したり、出力の値(量)やスクリプトを制限することができる。

このCovenantにはいくつかの制限がある。Elements Alphaのスクリプトでは、各スタックアイテムは520バイトに制限されているので、作成できるCovenantsはこのサイズの範囲内の< sigTransactionData >でなくてはならない。

再帰的なCovenants

続いて再帰的なCovenantsを構築する例を見ていく。

このCovenantsは、トランザクションに対しセットされる出力は1つだけで、その出力スクリプトは入力スクリプトと同じであるという制約を付与する。

署名されたトランザクションデータ全体をスタックに入れて解析するのではなく、このスクリプトトランザクションデータをピースから組み立てる。スクリプトはCovenantsを強制するために各ピースに条件を課すことができる。

以下は簡単な(と言っても長いけど)CovenantsのscriptPubkey

  1. <0x0100000001>
  2. OP_SWAP OP_SIZE 36 OP_NUMEQUALVERIFY OP_CAT
  3. <0x00> OP_CAT
  4. OP_SWAP OP_SIZE 32 OP_NUMEQUALVERIFY OP_CAT
  5. <0x00005f> OP_CAT
  6. 2 OP_PICK OP_SIZE 95 OP_NUMEQUALVERIFY OP_CAT
  7. <0xffffffff0100> OP_CAT
  8. OP_SWAP OP_SIZE 32 OP_NUMEQUALVERIFY OP_CAT
  9. <0x0000> OP_HASH256 OP_CAT
  10. <0x17a914> OP_CAT
  11. OP_SWAP OP_HASH160 OP_CAT
  12. <0x870000000001000000> OP_CAT
  13. OP_SHA256
  14. 1 OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT OP_DUP OP_CAT 1. OP_DUP OP_CAT OP_DUP OP_CAT
  15. OP_DUP
  16. 2 OP_ROLL 3 OP_PICK
  17. OP_CHECKSIGFROMSTACKVERIFY
  18. 1 OP_CAT OP_SWAP
  19. OP_CHECKSIG

このscriptPubkeyのロックを解除するscriptSigは↓

<recoveredPubKey>
<script>
<valueOut>
<valueIn>
<outPoint>

で、スタックをこの逆順で初期化すると

<outPoint>
<valueIn>
<valueOut>
<script>
<recoveredPubKey>

scriptPubkeyの最初の数ステップでは、scriptSigで提供される署名されたトランザクションデータの入力を再構築する。スクリプトトランザクションデータの各ピースが正しいサイズか検証する。

ステップ12までスクリプトを評価したら、スタックは↓のようになる。

<0x0100000001;outPoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash(0x0000);17a914;hash160(script);870000000001000000>
<recoveredPubKey>

スタックの一番上の値は、1つの入力と1つの出力を持つElements Alphaの署名済みトランザクションデータである。Bitcoinの署名済みトランザクションデータとフォーマットは少し異なる。署名されたトランザクションデータに、入力と出力の両方が与えられていることに注意する。
< hash(0x0000) >のデータは、Elements Alphaの秘匿トランザクションシステムの一部である。
<0x17a914> はP2SHトランザクションのプレフィックスであることが分かる。そのため指定する出力はP2SHアドレスでなければならない。
< script >が入力と出力両方にあるのが分かる。入力のscriptPubKeyは署名されたトランザクションデータのscriptSigを置き換える。 同じ< script > が入力と出力にあるので、これは入力のscriptPubkeyと同じscriptPubkeyに資金を移動することを意味する。結果、このCovenantsの制約が次のUTXOにも付与されることになり、再帰的なCovenantsになる。

ステップ13のOP_SHA256は、このトランザクションデータをSHA-256で1回ハッシュする。

<sha256(0x0100000001;outpoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash(0x0000);17a914;hash160(script);870000000001000000)>
<recoveredPubKey>

ステップ14では、<0x01...01>をスタックにプッシュする。この値は0x01を64個くっつけた値になる。Elements Alphaでは64バイトのシュノア署名が使われるため、<0x01...01>は固定したデジタル署名として使われる。
署名がスクリプトの入力として提供される代わりに、楕円曲線の公開鍵が入力として提供される。ステップ15のOP_DUPは、この署名を複製しスタックは以下のようになる。

<fixedSignature>
<fixedSignature>
<sha256(0x0100000001;outpoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash(0x0000);17a914;hash160(script);870000000001000000)>
<recoveredPubKey>

ここで、OP_CHECKSIGFROMSTACKを fixed signatureとトランザクションデータと公開鍵を使ってcovenantsを実行していく。まずステップ16で、スタック上に以下のように適切なアイテムを配置する。

<recoveredPubKey>
<sha256(0x0100000001;outpoint;00;valueIn;00005f;script;ffffffff0100;valueOut;hash(0x0000);17a914;hash160(script);870000000001000000)>
<fixedSignature>
<fixedSignature>
<recoveredPubKey>

ステップ17でOP_CHECKSIGFROMSTACKVERIFYがコールされると、OP_CHECKSIGFROMSTACKVERIFYが成功する< recoveredPubKey >の値が、fixed signatureとメッセージから、楕円曲線の公開鍵を使って計算できる。

<fixedSignature>
<recoveredPubKey>

ステップ18でSIGHASH_ALLのコードである0x01を署名に付与し、正しい順でスタックにプッシュする。

<recoveredPubKey>
<fixedSignature;SIGHASH_ALL>

最後にステップ19のOP_CHECKSIGで、スタック上に作成したトランザクションが実際のトランザクションデータと一致することを検証する。これでCovenantsは完璧になる。

↑のブログにはその他にMöser-Eyal-SirerのVaultをElements Alphaで実装するとどうなるかという説明がある。

実装

OP_CHECKSIGFROMSTACKの実装は↓で確認できる。

https://github.com/ElementsProject/elements/blob/alpha/src/script/interpreter.cpp#L1217-L1250

所感

  • 同じ公開鍵と署名を使ってOP_CHECKSIGFROMSTACKOP_CHECKSIGの検証を強制させることで、これから作るトランザクションの形式を強制させるというアプローチはおもしろい。
  • ただ、そのトランザクションデータの組み立てをOP_CATOP_SWAPなどを使って1つ1つ組み立てていくというのはスマートじゃないので別の仕組みがあった方がいい。
  • トランザクションデータの組み立ての煩雑さなどを考慮するとMöser-Eyal-SirerのOP_CHECKOUTPUTVERIFYを使ったアプローチの方がシンプルで良いと思う。
  • ただ、スタック上のデータの署名検証をするOP_CHECKSIGFROMSTACK自体はCovenants以外にもいろいろ利用できそう。

Bitcoinの使われ方を制限することで盗難を防ぐCovenants

ミラノで開催されたScaling Bitcoinのセッションの1つだったCovenantsについてホワイトペーパーが公開されてたので見てみた。

http://fc16.ifca.ai/bitcoin/papers/MES16.pdf

※ ↑の論文は特にBIPにもなっている訳でもないので、今後Bitcoinスクリプトシステムとして採用されるかどうかは未定。

Covenantsの概要

Bitcoinのセキュリティの大きな問題の1つが秘密鍵の管理方法。Bitcoinトランザクションの大半は、それを使用するのに秘密鍵を使った署名が必要で、秘密鍵が盗まれたらBitcoinも盗まれてしまうので、秘密鍵の管理は非常に重要だが、秘密鍵を安全に管理するというのは難しい。
そういった秘密鍵管理の問題に対して、このホワイトペーパーではCovenantsを使ったセキュアなVault(金庫)を提案している。Vaultは、攻撃者が秘密鍵を盗んでも攻撃者がその資金を完全にコントロールできない仕組みを導入することでユーザセキュリティを向上させるアプローチを取っている。

既存のBitcoinスクリプトではそういった制御はできないので、将来のBitcoinの使用を制限する新しいopcodeの追加を提案している。

CheckOutputVerify

トランザクションの出力は、Bitcoinの量(value)とスクリプト(scriptPubkey)で構成されており、この2つの構成要素を制御するのが新しく追加されるopcodeCheckOutputVerifyになる。CheckOutputVerifyは以下の3つの値を取る。

  • 出力のインデックス
  • 出力に設定するBitcoinの量(value)
  • 出力のスクリプト(scriptPubkey)のパターン

3つめのスクリプトのパターンは、変更可能な部分を示すプレースホルダを含むシンプルなスクリプトになる。プレースホルダには既存のBitcoinクライアントで既に使われているPubKeyPubKeyHashなどが利用可能で、これらは任意の公開鍵、公開鍵ハッシュを表現する。

CheckOutputVerifyで構成されたscriptPubkeyを持つUTXOを入力にセットしたトランザクションを作る場合、その入力は以下の検証をする。

  • スクリプトで指定されたインデックスの出力が存在するか
  • その出力の値が指定された量と一致するか
  • その出力のスクリプトが指定されたパターンと一致するか

1つでも条件を満たさない場合、スクリプトの評価は失敗することになる。また、量かスクリプトどちらかだけをチェックしたい場合、チェックしない項目は0をセットすれば検証されない。

例えばある 1 BTCが現実のある資産の所有権を表すとする。後続のトランザクションの出力が任意の公開鍵に対して正確 1 BTCを送ることを求める。この場合CheckOutputVerify

  1. 出力のインデックス0
  2. 1 BTCの量
  3. 公開鍵のプレースホルダの後にCheckSigが続くパターン

を供給することを求められるので、元のCovenantsは以下のようになる。

0 < 100000000 > < PubKey CheckSig > CheckOutputVerify

このCovenantsは、現実世界の資産に対応するBitcoinがそのまま(分割されることなく)譲渡され、他のコインと混合されることが無いことを保証する。

再帰的なCovenants

ただ↑のCovenantsは1回限りのものなので、1 BTCが任意の公開鍵に送られた後、そこからどうなるかは保証されない。それでは意味が無いので、後続のトランザクションの全連鎖にCovenantsを適用できるようにする必要がある。

そのため新しくPatternという新しいプレースホルダopcodeを追加し、パターン内にパターンを定義できるようにする。パターンを評価する際にPatternopcodeが出てきたら、そのプレースホルダはパターン自体に置換され、これによって再帰を実現する。↓はPatternの基本的な使い方。

< 100000000 > < <100000000 > Pattern CheckOutputVerify PubKey CheckSig > CheckOutputVerify < keyDest > CheckSig

スクリプトプログラム内のパターンを評価する際、Patternopcodeはパターン自体に置換され、それを使用する出力のスクリプトプログラムに同じパターンの適用を強制する。

カラードコインのようなコインの識別

Bitcoinプロトコルの上位レイヤとして、Bitcoin以外のアセットを発行しブロックチェーン上で流通させるカラードコインのような仕組みをCovenanstを使って実装する方法も紹介されている。

Bitcoinの仕様上、複数の入力と出力があるトランザクションの場合、入力のどのコインがどこに送られたのかをトラッキングすることはできないが、Covenantsを使えばある入力をある出力に割り当てることができるため、通貨の流れを意味を持たせてリンクさせることができる。ただ、Covenantsによって割り当てる出力のインデックスが明示されていると、複数の入力が同じインデックスを利用することになり、それによりどれか1つのアセット以外は無効化されてしまうという問題があるので、複数の入力を同じ出力に割り当て出来ないようスクリプトに一意の識別子(アセットID)を割り当てる必要がある。アセットIDを割り当てたパターンの例が↓

< assetId > Drop < value > Pattern CheckOutputVerify PubKey CheckSig

この時のCovenantsは↑のようになる。

< assetId > Drop < value > < ↑のパターン > CheckOutputVerify < keyDest > CheckSig 

Vaultの実装

Vaultはセキュリティを向上させるため2つの仕組みを導入している。

  • Vaultに保存されている資金について、Vaultの鍵が破損(盗難)された場合に備えてリカバリキーで回収する仕組み
  • リカバリキーが盗難された場合でも、資金の盗難を防ぐ仕組み

Vaultで使われるトランザクションは、攻撃者が本来の所有者のウォレットから資金を移動する際に、Bitcoinの移動を遅延させることで、資金の即時移動を制限している。どういうことかというと、Vaultから資金を出す際のトランザクションの出力に指定期間のロックを強制させることでこれを実現している。このロック期間中であれば、不正に取り出される前に所有者がリカバリキーを使って資金を回収できる。(リカバリキー自体はコールドストレージに保存されるのが望ましい。) またこのリカバリキーを使った資金回収の際も、Covenantsが再帰的に適用されまたVaultに資金がロックされるため、例えリカバリキーが盗難されても資金が盗まれることはない。

↓がVaultから資金を取り出す際に適用されるスクリプト

If
  < 100 > CheckSequenceVerify < keyDest > CheckSig
Else
  < 100000000 > < patternVault > CheckOutputVerify < keyRecovery > CheckSig
EndIf

解除条件は2つあり、1つはOP_CSVによる相対的なロックタイムを利用したもので、100ブロック経過したら資金を入手できる。もう1つは、リカバリキーによる署名があれば資金を入手できる。

このスクリプトを構成するためのPatternが↓

If
  < 100 > CheckSequenceVerify PubKey CheckSig
Else
  < 100000000 > Pattern CheckOutputVerify < keyRecovery > CheckSig
EndIf

patternVaultに記述するパターンは、Vaultに資金を保管するためのパターンで↓のようなものになる。

< 100000000 > Pattern CheckOutputVerify < keyVault > CheckSig

このためリカバリキーを使った資金の回収は、↑のパターンが適用されVaultへの資金の保管になる。

Bitcoin-NGのオーバーレイとしての実装

この他にCovenantsを使って、Bitcoinプロトコル上にBitcoin-NGプロトコルをオーバーレイとして実装するアプローチも提案されている。(ここでは詳細は省くのでBitcoin Covenantsのホワイトペーパー参照)

ポイズントランザクション

Bitcoin-NGのプロトコルで、選出したリーダーがマイクロブロックを複数作って、チェーンのフォークを発生させた際に、手数料収入を強制的に無効化するポイズントランザクションにCovenantsを使っている。

If
  < height +100+ t > CheckLockTimeVerify < pkLeader > CheckSig
Else
  < 90% of value > < Return > CheckOutputVerify
  < 10% of value > 0 CheckOutputVerify
  < pkPoison > CheckSig
EndIf

BitcoinがコインベースのUTXOを使用するのに100ブロック待つように、Bitcoin-NGもコインベースのUTXOを使用する際に100+tブロック待つ必要がある。IFブランチの条件が正当な報酬の受け取りで、ELSEブランチが不正が検知された場合の報酬になる。見ての通り、報酬の90%がOP_RETURNにより無効化する出力の作成がCheckOutputVerifyによって強制されているのが分かる。

不正の検出

Covenantsとは直接関係ないけど、↑のリーダーがマイクロブロックを複数作ってチェーンのフォークを発生させる不正の検知をする仕組みがおもしろい。

不正の検知にはECDSAの署名スキームのプロパティが利用されている。秘密鍵dを使って署名を作る際、署名プロセスの中で秘密乱数kを選択する。このkは一時的な鍵で、同じ秘密鍵を使って別のメッセージに署名する際に再利用してはならない(2つの署名からdを算出できるため)。全てのECDSAの署名にはkやその他の固定パラメータから計算されたrが含まれる。rからkを計算することは事実上不可能。

この特性を利用して、各キーブロックとマイクロブロックはrを公開し、次のマイクロブロックの一時的な鍵としてコミットする。後続のマイクロブロックを作成する際は、前のブロックのrとリーダーのポイズンキーを使って署名される。そのためリーダーが複数のマイクロブロックを作ってフォークを作る場合、リーダーが作成した両方のマイクロブロックの署名には同じrが使われているため、リーダーのポイズンキーを計算することができる。ポイズンキーが分かれば、不正行為の証拠として↑のELSEブランチを実行して報酬を破壊することができる。

所感

  • Bitcoinをアンロックする際に、後続のトランザクションに出力のパターンを強制させるというアプローチはおもしろい。
  • まだ議論されてるポイントみたいだけど、Covenantsで出力のインデックスを指定した際に、複数の入力が同じ出力を参照するといったケースが作られないような仕様にした方が良いと思う。
  • Bitcoin Coreにパッチとして実装してる感じで書いてたけど、どこで公開されてる?
  • Covenantsはこの他にBlockstreamがElementsで、Elementsの独自opcodeOP_CHECKSIGFROMSTACKVERIFYを使って実装する記事とかも出てたので、こちらも見てみたい。
  • 当然Covenantsの再帰を止めたいケースもあると思うので、そういった場合はタイムロックの仕組みなんかを利用して途中でCovenantsの再帰を終了できるように、Covenantsのスクリプトプログラマーがちゃんと設計してねというスタンスみたい。

Pay-to-contract プロトコル

サイドチェーンElements AlphaでBitcoinのブロックチェーンとサイドチェーン間で資金を移動する際に使われているPay-to-contractと呼ばれるプロトコルについてざっとホワイトペーパー読んでみた。

https://arxiv.org/pdf/1212.3257.pdf

Pay-to-contractプロトコルの概要

Pay-to-contractプロトコルは、売り手と顧客の間の決済を安全に行う仕組みとして提案されているプロトコルになる。

顧客がPCのブラウザでECサイトにアクセスして、安全にBitcoin決済をするユースケースを考える。顧客のPCにはウォレットはインストールされておらず、顧客のBitcoinは信頼できるハードウェアウォレットデバイスで管理されているとする。

よくある決済の手順としては

  1. 顧客がブラウザで、ECサイトに安全なSSL接続でブラウジングする。
  2. 顧客がECサイトで注文をする。
  3. ECサイトは自身のウォレットから支払先のBitcoinアドレスを選択する。
    (アドレスの秘密鍵ECサイトのサーバとは別にオフラインのキーウォレットに保存されているとする)
  4. 支払先のアドレスを顧客に通知する。
  5. 顧客はハードウェアウォレットを使って支払いを行うトランザクションに署名する。
  6. 顧客は署名したトランザクションBitcoinネットワークにブロードキャストする。
  7. 店舗側は決済が確認できたので商品の発送手続きに移る

攻撃のポイント

ここで悪意あるユーザからの攻撃のポイントを考える。
プロトコル開始前に顧客はECサイトの公開鍵をPKI等の別の仕組みを使って知っている前提での話)

ECサイトへの攻撃

もしECサイトが悪意あるユーザに侵入された場合、以下のことが考えられる。

  • 4でECサイトから顧客に通知される支払先のBitcoinアドレスを攻撃者のものに改竄する
  • 注文情報(配送先の住所など)を改竄する
  • 売り手のBitcoin秘密鍵ECサイト上で管理されていないので、売り手のBitcoinが盗まれるということはない。

この攻撃を防ぐには、決済プロトコルに署名の検証を組み込む。

注文情報をバンドルした請求書を作りECサイトが署名し、顧客に送る。顧客は署名を検証することで改竄の有無を検知できる。

ただ、ECサイトに侵入さているという状況では、その署名をECサイト上で行うことはできないので、別途ファイアウォール等で保護されたセキュアなデバイスを追加して、その上で秘密鍵の管理や署名を行う必要がある。

この場合の課題は、

などがある。

顧客への攻撃

もし顧客のPCが悪意あるユーザに侵入された場合、ECサイトとPC間で中間者攻撃が行われ

  • 注文情報(配送先住所など)を改竄する
  • ハードウェアウォレットと連携する際に偽のBitcoinアドレスにすり替える

といった攻撃が可能になる。

この攻撃を防ぐには、署名の検証を顧客のPCではなく、安全なハードウェアウォレット上で行うことで改善を検知することができ、検証プロセスの中で、請求内容を顧客に提示しインタラクティブな確認を求める。

※ この場合ECサイトの公開鍵はプロトコル開始前にハードウェアウォレット上に保存されている必要がある。ブラウザに同梱されているルート証明書のようにハードウェアウォレットにルートキーを同梱するなどの仕組みが必要。

プロトコル仕様

↑の課題に対して、ECサイト側のセキュアデバイスを追加することなく中間者攻撃の影響を受けることがない決済プラットフォームを考察するのがこのプロトコルのゴール。

Contract

このプロトコルでは顧客が実行する支払いを指定したContractと呼ばれる概念を導入している。通常ECサイトBitcoinによる支払いをする場合、明示的に支払先のアドレスが提示されるが、このプロトコルでは支払先のアドレスはこのContractから生成する。

具体的な仕組みとしては、ECDSAの準同型性を利用して、↓の関数を実装する。
※ P=ECサイトの公開鍵、K=ECサイト秘密鍵、H=ハッシュ関数、x=Contract

  • daddr(P, H(x))
    PとH(x)からユニークなBitcoinアドレスを生成する関数
  • dpriv(K, H(x))
    daddr(P, H(x)) に対応する秘密鍵を生成する関数

daddrでは公開鍵Pに対してContract x(正確にはそのハッシュ)を加算して新しい公開鍵を作りそこからアドレスを生成している。そしてdprivでは秘密鍵Kに対してContract xを加算して新しい秘密鍵を生成している。ECDSAの加法特性を利用することで、生成した公開鍵に対応する秘密鍵を生成している。

なお、これらの関数は既に実装されている決定性ウォレットを利用することで期待する値が得られる。

Contract x の支払先のアドレスをb := daddr(P, H(x))とした時、顧客はContract x とECサイトの公開鍵からbを計算することができ、秘密鍵Kを保有するECサイトの事業者だけがbに送られたコインを入手することができる。

またContractはアドレスだけでなく、顧客にとって支払いの証明にもなる。ただ証明の際にContractを開示する必要があるが、Contractには配送先住所などのプライベートな情報も含まれているため、そういったデータを除外した形で支払いの証明をできるようにしたい。そのためContractをまーくるツリーとして構成することで、Contractの全データを開示することなく支払いの証明を行うことができる。

実際にContractに含むデータのレイアウトは店舗側で自由に決められるが一般的には↓のようなデータが考えられる。

  • 静的なデータ
    価格、売り手のサービス条件、保証、売り手の監査人の名前など
  • 動的なデータ
    品目番号、数量、価格、納品先住所、納期、支払先住所、支払い期限、クライアントを参照/トラッキングするトークン、売り手を参照/トラッキングするトークンなど

※静的なデータはContractを構築する前にECサイトの事業によって署名されている。

Contractを使ったプロトコル

Contractを使ったプロトコルは↓のようになる。

  • 顧客からの注文を受けてECサイトはContract xを生成し顧客に送る。
  • xにはECサイトの公開鍵も含まれ、顧客は注文が内容通りか確認し、問題なければハードウェアウォレットに転送する。
  • ハードウェアウォレットはx内の全ての署名を検証し、公開鍵とxの内容を顧客に表示する。
  • 顧客が内容を承認すると、ハードウェアウォレットはb := daddr(P, H(x))を計算し、そのアドレスにxに記述された金額を支払うトランザクションを作成し、Bitcoinネットワークにブロードキャストする。

この時顧客はハードウェアウォレットが表示したxの内容を確認するため、コインの支払先は必ず正しいアドレスdaddr(P, H(x))になる。

仮にxが改竄されx'になったとして、その支払先のアドレスはdaddr(P, H(x'))になる。この場合、ECサイト側はこのアドレスへの支払いは検知しないので、商品が発送されることは無い。商品が発送されないので、後日顧客は別の連絡手段でECサイトに問い合わせ支払いの証明x'を開示する。この場合x'に送られたコインを入手できるのはいずれにせよECサイトの事業者のみであるため、攻撃者に資金や商品が盗まれることは無い。

結果、攻撃者ができる攻撃はDoS攻撃くらいになる。

ここまでが Pay-to-contractプロトコルの基本概念になる。

オフライン決済

↑のように支払先のアドレスはContract xから計算されるので、例えばリピート等で再注文をする場合、顧客はContract xを既にしっているため、ECサイトにアクセスすることなく、daddr(P, H(x))への支払いトランザクションを作成しブロードキャストするだけで注文が完了する。(ただし、実商品の場合、在庫チェックなどは行えない)

ということは Contract xさえ作れればECサイトは別に必要ないということになる。予めEC事業者が署名済みのContract Formをパブリックなスペースに公開しておけば、顧客からのEC事業者の一方向の通信のみで決済が可能になる。
(ただし、何らかの方法でEC事業者の公開鍵を事前に顧客が知っている必要はある。)

分散ファイルシステムとDH鍵交換を使った秘匿取引

EC事業者が出来る限り匿名のまま自分の活動を隠したい場合、オフラインでかいつ第三者のオブザーバーに気付かれることなくContractを償還する↓の2つからなるプロトコルも提案されている。

このプロトコルは↓のようなものになる。(Contract xを入手した後に)

まず顧客側で↓の手順を実行する。

  1. ハードウェアウォレットでdaddr関数を使ってContract xへの支払いアドレスを計算する
  2. DH鍵合意で共通鍵を生成する
    (顧客の秘密鍵と事前に入手しているEC事業者の公開鍵から作成)
  3. トランザクションを作成し共通鍵でタグ付けする
    (このトランザクションの入力か出力のどちらかに顧客に公開鍵を入れる)
  4. トランザクションに署名しBitcoinネットワークにブロードキャストする
  5. 共通鍵からファイル名を生成する
  6. Contractを共通鍵(対称鍵)を使って暗号化する
  7. 暗号化したContractを5で生成したファイル名で分散ファイルシステムにポストする

その後、EC事業者は↓の手順を実行してContractを償還する。

  1. ブロックチェーンを関しし、タグ付きのトランザクションを識別する
  2. タグ付きのトランザクションから共通鍵を導出する(EC事業者の秘密鍵トランザクション内の顧客の公開鍵から作成)
  3. タグからファイル名を導出して分散ファイルシステムを検索する
  4. 分散ファイルシステムからデータを取得し、共通鍵で解読したContractを入手する
  5. Contractから支払先のアドレスを計算する
  6. 計算したアドレスとトランザクションのアドレスの出力が等しければ支払いは正しいので、顧客からの注文を受け入れる

所感

  • 支払先のアドレスを提示するのでなく、顧客側にContractから計算させるアプローチをとることで、中間者攻撃からコインや商品を守ってる。
  • データとその証明にマークルツリーを利用するのはありだなー。
  • このプロトコルに限った話でもないけど、公開鍵をPKIなどで事前に共有する仕組みは必要で、ブロックチェーン単体では認証はできない。
  • 最後のDH鍵交換の部分でタグ付けしたトランザクションの識別方法はどうやってるんだろう?

Elements AlphaにおけるFederated Pegの仕組み

Blockstreamが開発しているサイドチェーン実装のElementsで実装されているFederated Pegの技術的な仕組みが↓で公開されてたので見てみる。(記事自体は2016/02/29に書かれたもの)

The Federated Peg in Elements Alpha — The Elements Project

インチェーンとサイドチェーン間の資金の移動は以下のようなイメージで行われる↓

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

各プロセスの具体的な仕組みを↑のドキュメントでおさえていく。

Federated Pegの仕組み

OP_WITHDRAWPROOFVERIFY (OP_WPV)

まずAlphaのgenesis blockから見てみる。Alphaのgenesis blockのcoinbase↓

{
    "hex" : "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff00000000000000000100000000000000000000000000000000000000000000000000000775f05a074000000037206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000149eac001049d5c38ece8996485418421f4a01e2d7b300000000",
    "txid" : "0377d218c36f5ee90244e660c387002296f3e4d5cac8fac8530b07e4d3241ccf",
    "version" : 1,
    "locktime" : 0,
    "fee" : 0.00000000,
    "vin" : [
        {
            "coinbase" : "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73",
            "sequence" : 4294967295
        }
    ],
    "vout" : [
        {
            "value" : 21000000.00000000,
            "serValue" : "00000000000000000000000000000000000000000000000000000775f05a0740000000",
            "n" : 0,
            "scriptPubKey" : {
                "asm" : "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 9eac001049d5c38ece8996485418421f4a01e2d7 OP_WITHDRAWPROOFVERIFY",
                "hex" : "206fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000149eac001049d5c38ece8996485418421f4a01e2d7b3",
                "type" : "withdraw"
            }
        }
    ],
    "blockhash" : "f7f0ca371b1003dc7346bab766b4a131f9e3a5d68820a364d70921cb15b95eaa",
    "confirmations" : 304446,
    "time" : 1296688602,
    "blocktime" : 1296688602
}

Bitcoingenesis blockとはだいぶ違って、1つの出力に2100万の値を持っており(Bitcoinの総発行量と同値)、そのscriptPubKeyも↓のように変わったスクリプトになっている(scriptPubKeyのタイプもwithdraw)。

6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000
9eac001049d5c38ece8996485418421f4a01e2d7
OP_WITHDRAWPROOFVERIFY

この出力はwithdraw lockと呼ばれている。OP_WITHDRAWPROOFVERIFY (OP_WPV) は新しいopcode。
Alphaにはマイニング報酬が無いため、Alphaのコインを入手するための唯一の手段は、このwithdraw lockを使うことになる。実際にwithdraw lockを使用するトランザクションwithdrawal transactionを作るケースを考えてみる。

OP_WPVはスタック上に以下の要素があることを想定している。

  1. HASH160(secondScriptPubKey) 、secondScriptPubKeyのチェックのために使う
  2. インチェーンのgenesis blockのハッシュ
  3. locking transactionが含まれているブロックのcoinbase tx
  4. 使用しようとしているlocking transactionの出力のインデックス
  5. locking transaction
  6. locking transactionが含まれているブロックのマークルブロック構造
  7. コインを送信しようとしているコントラクト
  8. secondScriptPubKeyを満たすscriptSig
  9. secondScriptPubKey

最初の2つの引数(secondScriptPubKeyのハッシュと、genesis blockのハッシュ)はscriptPubKeyによって提供されている。これらにはチェーン固有の情報が追加されており、このケースでは後述するscripthashとbitcoingenesis blockハッシュになる。↑の

6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000

エンディアンを変換すると

000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f

となり、Bitcoingenesis blockのハッシュと同じ値になる。

その次の要素はlocking transactionを提供している。これは、サイドチェーン上でコインを取り出すために、メインチェーン上のコインをロックするメインチェーンのトランザクションである。以下の図はこれまで出てきたトランザクションタイプの関係を表したものになる。

https://elementsproject.org/img/the-federated-peg-in-elements-alpha/peg.png

withdrawal transactionは、他のチェーン上のロックを参照して、OP_WPVでロックされたコインを使用する。メインチェーンにはOP_WPVというopcodeは無いので、OP_WPVを使う代わりにメインチェーンでは連合のコントロール下にあるマルチシグ出力でロックする。語彙の対称性に注意すること(メインチェーンで現在アンロックされているコインは、サイドチェーン側ではロックされている状態にある)。

スクリプトインタプリタlocking transactionの宛先が本当に連合のマルチシグかどうかチェックする。さらに、locking txcoinbase txがマークルブロック構造の中に含まれているかチェックし、コインベースからブロック高を読み取る。Elements Alphaは非対称ペグを使用しているため、Alphaのフルノードは親チェーンと同様完全なバリデータであることが期待される。locking transactionBitcoinのブロックチェーン上に存在することを検証するために、AlphaはRPCを介してbitcoindに接続し、getblockをコールしてトランザクションがブロックチェーンに記録され10回承認されているかチェックする。将来的にOP_WPVは、bitcoindに依存することなくブロックが充分な承認がされているかどうかチェックするために、SPV証明を受け入れる。

次にOP_WPVは16バイトのnonceからなるcontractの宛先とwithdrawal transactionの受信者を表す20バイトのscript hashを検証する。インタプリタpay-to-contract (P2C)プロトコルを使ってlocking transactionコントラクトにコミットしているかどうかチェックする。P2Cを使うと、locking transactionの送信者は透過的に宛先のscript hashを指定することができ、バリデータはサイドチェーン側でコインを取り出す受信者をチェックできる。これはコントラクトのために新しいキーペアを導出することで動作する。

x: privkey = old_privkey + H(x), pubkey = old_pubkey + H(x)*G.

要約すると、OP_WPVでロックされた出力を使いたいユーザは、最初にサイドチェーン側でP2SHアドレスを作成し、サイドチェーンに移動する資金をメインチェーン上で連合に送るlocking transactionを組み立てる。続いて、(contract形式の)script hashをlocking transactionに埋め込みブロードキャストする。ブロードキャストしたトランザクションの確認期間(10ブロック)が経過したら、locking transactionコントラクトを使ってOP_WPVを使うことができる。

Withdraw output

OP_WPVは、それを使用するトランザクションの出力へのアクセス権を持つopcodeの新しいタイプである。OP_WPVは、トランザクションwithdraw outputという出力と、おつりを表すOP_WPV-locked outputという2つの出力を持っているかチェックする。次にインタプリタは、出力の値を比較し、withdraw outputの資金の量がメインチェーンでロックされた資金より多くないか、残りのおつりがロックされた出力に戻るようになっているか検証する。このおつりは将来また↑のようにメインチェーンからサイドチェーンへ資金を移動する際のトランザクションで使われる。

withdraw outputには↓のscriptPubKeyが設定されている。

OP_IF
    <nLockHeight>
    <lockTxHash>
    <nLocktxOut>
    <fraudBounty>
    <HASH160(secondScriptPubKey)>
    <genesisHash>
    OP_REORGPROOFVERIFY
OP_ELSE
    144
    OP_CHECKSEQUENCEVERIFY
    OP_DROP
    OP_HASH160
    <HASH160(destinationScript)>
    OP_EQUAL
OP_ENDIF

まずELSEブランチを見てみる。OP_CHECKSEQUENCEVERIFYによりこのwithdraw outputがブロックに格納されてから144ブロック(24時間)経過するまで(contest period)、このブランチが実行できないようになっている。contest periodが過ぎたら、withdraw outputは通常のP2SHスクリプトになる。インタプリタはさらに、destination scriptHashがlocking transaction内でコミットされたcontractから作られたscript hashか確認する。

最初のブランチの実行を成功させるには、2つめの新しいopcode OP_REORGPROOFVERIFY (OP_RPV)に不正の証拠を提供する必要がある。そのため、contest periodの間、withdraw outputは任意のノードから不正の証拠を受け入れることができ、それによりこの出力をアンロックし、fraud bountyを収集する。

OP_REORGPROOFVERIFY (OP_RPV)

OP_RPVはメインチェーンのロックの二重使用と(チェーンが分岐し再収縮するケースなどによる)メインチェーンの再構成という2つの無効な状態を修正するためのopcodeである。同じメインチェーンのロックを参照したwithdrawal transactionsを作ることは可能で、以下の図はメインチェーン上のlocking transactionを使ってサイドチェーン側で二重使用しようとする例。

https://elementsproject.org/img/the-federated-peg-in-elements-alpha/peg-fraud.png

OP_RPVは、不正の証拠が提供された際、withdraw outputをアンロックできる(↑のscriptPubKeyの最初のブランチ)。メインチェーンのロックへの参照は既にwithdraw outputに与えられているため、インタプリタは実際に二重使用があるか、オリジナルのwithdraw transactionと二重使用のwithdraw transaction共にマークルブロックが有効で、二重使用のwithdraw transactionがオリジナルのwithdraw transactionより新しいかチェックする。Elements Alphaのブロックチェーンのコンセンサスは署名者の連合によって決定するため、インタプリタは唯一有効な署名を持つブロックを必要とする。もしAlphaのコンセンサスがPoWであれば、OP_RPVは(OP_WPVがブロック高の検証にSPV署名を使うのと同様)検証のためにSPV証明を必要とする。

OP_WPVと同様、OP_RPVもアンロックするトランザクションの出力を参照する。トランザクションwithdraw outputの値からfraud bountyを差し引いた値を再ロックするため、OP_WPVを使った1つの出力を持つ。fraud bountyは不正の証明者によって請求される。

名前が示すようにOP_RPVは二重使用があるときだけでなく、メインチェーンが再構成された際にも使われる。そのような状況では、lockin transactionは他のブロックに含まれるか、チェーン上から完全に消えており、いずれの場合も再構成の証拠によってwithdraw outputは無効化される必要がある。これはまだAlphaでは実装されてないが、原則的に証明者が充分時間が経過した後、withdrawal transactionが含まれていたブロックからwithdrawal transactionが消えていること示すSPV証明を提供すると思われる。

alphadの起動オプションにtracksidechain=allを指定していると、二重使用が発生した際に不正の証拠を提供するため全てのメインチェーンのロック出力を保存する。

最後に、OP_WPVの9つめのsecondScriptPubKeyについて見てみよう。secondScriptPubKeyは、scriptSigの実行結果やwithdrawal transactionに関する情報を含むスタックで実行される。Elements Alphaでは、BitcoinをロックするsecondScriptPubKey

OP_DROP 144 OP_LESSTHANOREQUAL

で、これで効果的にcontest periodを経過したことを確認できる。secondScriptPubKeyはscriptSigの結果をチェックしないので、この場合のデフォルトのscriptSigはちょうど1バイトのプッシュになる。この仕組みは原則的にトランザクション手数料やfraud bounty、その他の複雑なルールにチェーン特有の制限を加えることができる。

サイドチェーンからメインチェーンへの移動

サイドチェーンからメインチェーンへ資金を移動する仕組みは比較的単純。以下の図は、メインチェーンへ資金を移動する際のトランザクションのシーケンスを表している。

https://elementsproject.org/img/the-federated-peg-in-elements-alpha/peg-out.png

インチェーン上のfederated lockはm-of-nのマルチシグの出力である。ロックを保持する職員はサイドチェーン上でメインチェーンへの払い戻しのリクエストが来ると、RPCを介してalphadと通信しロックを解除するwatchmanというプログラムを実行する。

払い戻しのリクエストはロック出力の形式をとり、サイドチェーンからメインチェーンへの転送は以下のscriptPubKeyを持つ新しいトランザクションで始まる。

<HASH160(scriptDestination)>
OP_DROP
<genesisBlockHash>
<HASH160(secondScriptPubKey)>
OP_WITHDRAWPROOFVERIFY

このスクリプトはサイドチェーンのコインをロックし、メインチェーンのアドレスを伝える。これによりロックされたコインを意図した受信者に入金するよう職員に指示する。

この仕組みの裏側では、watchmanが別のネットワークを使用してround-basedの分散プロトコルに従い、round-leaderを選ぶ。round-leaderは、払い戻しのリクエストを集めて対応するメインチェーンのwithdrawal transactionを作成する。round-leader以外の他の職員は、そのトランザクションが払い戻しのリクエストに正しく対応しているか(宛先は正しいか、サイドチェーンのロックはn confirmされているかなど)チェックし、署名する。充分な署名が集まったらwithdrawal transactionは有効になり、Bitcoinネットワークにブロードキャストする。

この際、連合はトランザクション手数料を支払わなければならない。そのためユーザに寄付をしてもらう仕組みがいくつかある(サイドチェーンでOP_RETURNの出力を作るか、直接連合のアドレスにコインを送ってもらうか)。

まとめ&所感

  • Elements AlphaにはBitcoinと違ってマイニング報酬はなく、genesis blockのコインベースにあるコインが全てで、このコインベースにはBitcoinの発行総量と同じ2100万のコインがセットされている。
  • Federated Pegを実現するため、AlphaにはOP_WITHDRAWPROOFVERIFYOP_REORGPROOFVERIFYという2つの新しいopcodeが追加されている。
  • OP_WITHDRAWPROOFVERIFYはメインチェーンからサイドチェーンに移動した資金を入手する際に使われるopcode。
  • OP_REORGPROOFVERIFYは、メインチェーンでロックした資金をサイドチェーン側で二重使用しようとした場合や、メインチェーン側のブロックチェーンが再構成されlocking transactionが別のブロックになったり、チェーンから消えてしまった際の対応をするためのopcode。
  • AlphaではOP_WPVを使ってメインチェーン側のロック状況を参照した上でサイドチェーン側のコインのアンロックしている。
  • BitcoinにはOP_WPVといったopcodeは無いので、連合が管理するマルチシグで代替する。
  • サイドチェーン側で資金を入手する際はOP_WPVによる検証が行われ、ロックされたコインを入手する出力と、再度ロックされるおつり分の出力の2つの出力を持つトランザクションを作成する。
  • サイドチェーン側に移動する際の二重使用や、メインチェーン側の再構成を考慮して、メインチェーンからサイドチェーンに移動したコインは、使えるようになるまでに144ブロック(24時間)かかる。
  • AlphaのコンセンサスはPoWではなく、連合の署名によって決められる。
  • サイドチェーンからメインチェーンへの移動は、サイドチェーンのコインをwithdraw lockするトランザクションを作り、それが検証され、連合の職員によりメインチェーンのBitcoinのマルチシグをアンロックするトランザクションが署名&ブロードキャストされる。
  • 現時点でOP_WPVはロッキングトランザクションのブロックのconfirmationの確認にbitcoindを使ってて将来的にSPV証明を利用する形になるってあるので、現状はOP_WPVスクリプトインタプリタ内でbitcoindにRPCしてるっぽい。
  • SPV証明使う機能は将来的に組み込むと書かれてるけどまだ組み込まれてない?
  • Pay-to-Contract(P2C)の内容がよく分かってないのでホワイトペーパー読む。
  • OP_REORGPROOFVERIFYを使って不正の証拠を提供するって具体的にどういうデータを提供すればいいのか?

Segwitのアップグレードガイド

mainnetへのSegwitデプロイを有効にするBitcoin Core 0.13.1がリリースされ、それに合わせてSegwitをサポートしたい人、したくない人向けにアップグレードガイドも公開されてたので見てみる↓
malleabilityの問題や署名の前後でtxidが変わらないのはコントラクトを作る上でも必要な機能なので個人的にはSegwitのリリースは歓迎だけど、Segwitサポートしたくない人向けにも書いてるのが素晴らしい。

bitcoincore.org

Segwitのソフトフォークは、Segwitを採用するかどうか個人の決定にまかせていて、このガイドはSegwitを採用したい人、採用したくない人向けに両者に向けて書かれている。

ソフトフォークのアクティベーションに十分なマイナーの同意が得られたら、Segwitはアクティベーションされ、ユーザはsegregated witnessesなトランザクションを作ることができるようになる。Segwitのソフトフォークは一般的に使われているウォレットにとって前方・後方互換がある形で設計されており、ウォレットの開発者やユーザはそれぞれSegwitを採用するるか採用せずに今まで通りのトランザクションを作るか自由に決められる。

マイナー

このセクションはソロマイナーとマイニングプールのオペレータ向けに書かれている。プールマイナーはSegwitアップグレードするかしないかプールオペレータに問い合わせること。

2016年7月4日にアクティベートされたBIP 68/112/113と同様、BIP-9のソフトフォークのデプロイの仕組みがSegwitにも使われている。アップグレードするかしないか、アップグレードプロセスの重要な段階を理解する必要がある。

  • Started
    Segwitは2016年11月15日以降最初のretarget periodが始まる。これはアクティベートされるか、1年後までにlock-inステータスにならず失敗したと判断されるまで続く。この間、マイナーはブロックヘッダのversion bits 1 にフラグをセットすることでSegwitの新しいコンセンサスルールをサポートすることを表明することができる。
  • Locked-in
    2016ブロック内の95%のブロックがSegwitのサポートを表明すると、Segwitのソフトフォークはロックインされ、そこから2016ブロック(約2週間)後にアクティベートされる。
  • Activated
    ロックイン期間が終わると、マイナーは署名が分離されたトランザクションを含むsegwitスタイルのブロックを生成する。

アップグレードしない場合

startedフェーズでは、Segwitを採用したくない場合はBitcoin Core 0.13.1のようなSegwit互換のノードへのアップグレードをせずSegiwtのversion bits 1にフラグをセットするのを避ける。

もしソフトフォークがlocked-inフェーズに入ったら、すぐにアップグレードする必要は無いがノードのアップグレードを推奨する。Segwitのソフトフォークはsegwitスタイルのブロックの生成を強制するものではないので、今まで通りのブロックの生成を継続することができる。しかし、他のマイナー達の支持によりステータスがactivatedになると、あなたは有効と判断しているブロックもsegwitを支持する各ノードはリジェクトし、あなたの作ったブロックは無効であると判断される。

そのためsegwitがlocked-inになった後は、フルノードをBitcoin Core 0.13.1にアップグレードするか、後述するフルノードをアップグレードしない場合に記載されているようにsegwitのフィルタとしてBitcoin 0.13.1もしくはそれ以降のバージョンを使用することを推奨する。

アップグレードする場合

マイナーは2016年11月15日以降、BIP-9のルールに従ってSegwitのサポートを表明することができる。Segwitのサポートを表明するのは以下の作業が必要になる。

  • ブロックに含めるトランザクションの選択とブロックの構成をするのにBitcoin 0.13.1もしくはSegwit互換のフルノードにアップグレードする。
  • マイニングソフトウェアもしくはマイニングプールのソフトウェアをSegwit互換のバージョンにアップグレードする。
  • ブロックを生成する際に、BIP-9に従い、segwitのversion bits 1をセットする。

Segwitがアクティベートされると、Segwitスタイルのブロックのマイニング及びリレーができるようになる。

GetBlockTemplate (GBT)のRPCをサポートするソフトウェアは、BIP-9とBIP-145によるGBTの変更に対応するため必ずアップグレードする必要がある。

Segwitは既にtestnetではアクティベートされているため、testnetを使えば少ないハッシュレートでアップグレードしたインフラでマイニングのテストをすることができる。またBitcoin Core 0.13.1のregtestもデフォルトでSegwitをサポートしている。

ルノードユーザ

ルノードはBitcoinのコンセンサスルールに違反するブロックからユーザを保護する。Segwitのようなソフトフォークを使ったアップグレードでは、新しいルールが追加されても、アップグレードしていないノードはそのルールについて知らない。これは問題ではなく、Segwitのソフトフォークはアップグレードしていないユーザもソフトフォークの前と同じ方法で引き続きBitcoinを利用できるよう設計されている。

しかし、Segwitのソフトフォークで有効になった機能を使いたい人は、充分な数のフルノードユーザがSegwitのルールに違反するブロックやトランザクションを拒否するためノードをアップグレードしたか知りたいだろう。それによってマイナーにSegwitによって更新されたコンセンサスルールに従うインセンティブを提供する。

アップグレードしない場合

マイナーではなくSegwitのアップグレードをしたくない場合、単純に今のフルノードをそのまま使い続ければ良い。Segwitはソフトフォークで実装されているので、アップグレードする必要は無い。またフルノードに接続しているウォレットもアップグレードする必要はなく以前と変わらず利用できる。

しかし、承認数の少ない(1とか2の)トランザクションを受け入れる場合、ソフトフォークがアクティベートされた後は、アップグレードしていないノードが一時的に無効ブロックを受け入れる小さなリスクがあることに注意する必要がある(一部のアップグレードしていないマイナーが作ったブロックが中継されてくる可能性があるため)。その状況はアップグレードしたマイナーがSegwitのコンセンサスルールを継続して強制するため数ブロックで解消するが、無効ブロックで承認されたトランザクションが、その後の有効なブロックで承認されている保証は無い。

この問題を防ぐ一番簡単な方法は、 Bitcoin Core 0.13.1もしくはSegwit互換のフルノードにアップグレードすること。それでもアップグレードしたくない場合は、以下のように、古いBitcoin Coreのフィルタとして新しいBitcoin Coreを使うことができる。

https://bitcoincore.org/assets/images/filtering-by-upgraded-node.svg

この構成では、現在のBitcoin Coreのノード(古いノード)をBitcoin Core 0.13.1以降のノード(新しいノード)にのみ接続するよう設定する。新しいノードの方は通常通りBitcoinP2Pネットワークに接続する。新しいノードはSegwitのコンセンサスルールの変更の中身を知ってるので、古いノードによって作られた無効ブロックを中継することは無い。

この構成にする際は、Bitcoin Coreのデフォルトを利用している場合、古いノードはSegwitの機能を使ったトランザクションをブロックに入るまで見れないという点に注意する必要がある。

ノードの設定

新しいノードは、普通に起動してブロックチェーンを同期する。現時点でprunedノードは中継ノードとして使えないので、この構成ではprunedノードは使えない。古いノードを特別なノードとして扱うため、以下のパラメータを付けて起動する(もしくは設定ファイルに記述)。

  -whitebind=<addr>
       Bind to given address and whitelist peers connecting to it. Use
       [host]:port notation for IPv6

  -whitelist=<netmask>
       Whitelist peers connecting from the given netmask or IP address. Can be
       specified multiple times. Whitelisted peers cannot be DoS banned
       and their transactions are always relayed, even if they are
       already in the mempool, useful e.g. for a gateway

古いノードは新しいノードのブロックチェーンの同期が終わったら、以下のコマンドラインパラメータを付与して再起動する。

-connect=<新しいノードのIPもしくはDNS名>

これで古いノードは新しいノードにのみ接続され、全てのブロックとトランザクションは新しいノードでフィルタリングされる。

アップグレードする場合

Segwit互換にアップグレードするには、Bitcoin Core 0.13.1のようなSegwit互換のフルノードをダウンロードし、ダウンロードしたファイルが正しいものか(PGPなどで)確認し、旧バージョンのノードを停止し、新しいバージョンのノードを起動する。Segwitがアクティベートされた後にアップグレードした場合、(旧ノードではダウンロードしていないデータがあるため)アクティベーションされた時点からのブロックを再同期する必要があるので注意すること。

Bitcoin CoreのRPCgetblockchaininfoを使うとSegwitのソフトフォークの状況が確認できる。この情報から、最近のブロックのどれくらいがSegwitのコンセンサスルールを適用しようとしているかが分かる。またgetblockchaininfoの結果から、Segwitのソフトフォークがいつlocked-inactivatedになったのか分かる。

Bitcoin Core 0.13.1のウォレットは、デフォルトでBitcoinの支払いを受け取るため非SegwitのP2PKHのアドレスを生成する。今後のリリースでユーザがSegwitなアドレスで支払いを受け取るか選択できるようになることが期待されている。

もし開発者やエキスパートでテストのためにアドレスを生成したい場合はSegwitの開発ガイドを参照。

ウォレットのユーザ

アップグレードしない場合

Segwitにアップグレードしたくない場合は、Segwitサポートを追加していない任意のウォレットを使えば良い。アップグレードしなくても、Segwitにアップグレードしたユーザとも、アップグレードしてないユーザとも取引をすることはできる。

アップグレードしない場合に唯一違うのは、Segwitに対応したユーザからの支払いを受ける場合、そのトランザクションがブロックに含まれるまであなたのウォレットには表示されない。これはマイナーによって承認されるまで未確認の取引からあなたのウォレットを保護する安全機能である。

アップグレードする場合

Segwitにアップグレードしたい場合は、マイナーがSegwitをアクティベースするのを待ち、Segwitスタイルの支払いや受け取りをサポートするウォレットが必要になる。Bitcoin Coreのウォレットや軽量ウォレット、サードパーティが管理しているウォレット全般に言える。Bitcoin Coreもしくは別のフルノードのユーザは、上記のフルノードのセクション参照。

Segwitをサポートするウォレットにアップグレードしたら、Bitcoinの受け取りようのアドレスとして3で始まるアドレスが生成される(P2SHアドレス)。

一般的なウォレットはほとんどP2SHアドレスへの支払いができるため、他のユーザがSegwitにアップグレードしているかどうかに関係なく、P2SHを使って支払いを受け取ることができる。Segwitにアップグレード後にBitcoinの支払いをする場合、元々の1から始まるBitcoinアドレス(P2PKHアドレス)に支払いをすることができる。

Segwit対応のウォレットを使ってBitcoinを送ると、次のような点が分かる。

  • アップグレード前に受信したBitcoinを使う時に作られるトランザクションはSegwitのアップグレード前に作られるトランザクションとなんら変わりはない。
  • アップグレード後に受信したBitcoinをSegwitのアップグレードしていないユーザに送る場合、そのトランザクションはブロックに入れられるまで相手には表示されない。これはトランザクションがブロックに含まれるまで、このトランザクションを完全に理解できないウォレットにそのトランザクションを示すことを回避する安全機能である。トランザクションが承認されると受領したBitcoinが見れるようになりいつもどおりに使うことができる。
  • アップグレード後に新しいP2SHアドレスで受信したBitcoinを使う際、非SegwitなUTXOを使った時より若干手数料が安いことに気付くだろう。これは、署名が含まれているトランザクションの一部のデータにBitcoinのフルノードが迅速にアクセスする必要が無いため、Segwitによりマイナーは非witnessなデータの4倍のwitnessデータを保存できるようになっている。そのためブロック作成コストとの調整で手数料が安くなっている。

Bitcoinのソフトウェア開発者

全てのBitcoinのソフトウェアは以前と同様に動作するべきで、Segwitの新しい機能のアドバンテージを利用したい場合はソフトウェアをアップグレードする必要がある。

Segwitの開発者のためのドキュメントが↓

Segwitのアドレスフォーマットを定義したBIP-142のステータスはdeferredで、標準ではないので注意すること。代わりにbitcoin-devのMLで現在のBase58エンコードしたアドレスより使いやすい新しいBitcoinのアドレスフォーマットについて議論されている。

BIP141、143、144、および145のほとんど実装はBitcoin CoreのPR#8149で確認できる。また、BIP-147の実装はPR#8636

Segwit対応のテストは、testnetがSegwitのサポートをしており、ここ数ヶ月の間で多数のSegwitのブロックができている。またSegwitのブロックの最大サイズ(4MB)に近いサイズのブロックも作られている。Bitcoin Core 0.13.0と0.13.1のregtestもデフォルトでSegwitをサポートしている。

参考

参考までに関連するBIPを以前意訳したもの↓

techmedia-think.hatenablog.com

techmedia-think.hatenablog.com

techmedia-think.hatenablog.com

techmedia-think.hatenablog.com

所感

  • Segwit対応したBitcoin Coreのウォレットでは、受信用のアドレスはP2SHになるみたい(P2WPKHをネストしたP2SH)。
  • Segwitのアドレスを定義したBIP-142は結局実装に含まれてなく、支払いの受け取りアドレスもP2PKHアドレスを引き続き生成してるので、Segwitなアドレスが定義されるまではP2WPKHやP2WSHとかのトランザクションは少なく、基本はP2SHでネストしたアドレスが主流か?
  • まだブロックに含まれていないトランザクションも、そのトランザクションデータを受信すれば0 confirmationのUTXOとして認識できるけど、Segwit互換のノードにアップグレードしていない場合は、Segwitのトランザクションはブロックに入れられるまで認識できなくなると。
    0 confirmationで決済してる事業者とかは事前にSegwitへのアップグレードが必須。
  • Segwitのリリースだけじゃなく、BIP-147も一緒にデプロイされると。