Develop with pleasure!

福岡でCloudとかBlockchainとか。

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も一緒にデプロイされると。

マルチシグ検証の際のmalleabilityを解消するNULLDUMMYルール(BIP-147)

Segwitと一緒にデプロイされるBIP-147を見てみる。

bips/bip-0147.mediawiki at master · bitcoin/bips · GitHub

このBIPでは、OP_CHECKMULTISIGOP_CHECKMULTISIGVERIFYで使われる余計なスタック要素を使ったmalleabilityを解消するため、Bitcoinトランザクションの検証ルールの変更を提案している。

動機

署名を使ったmalleabilityは、作られた署名のベースとなる秘密鍵へアクセスすることなく、ネットワーク上の任意の中継ノードがトランザクションの署名の改変をする能力によるものである。Segwitによる署名が分離されていないトランザクションでは、このmalleabilityを利用してtxidを変更し、任意の未承認の子トランザクションが無効にすることができる。BIP-141のSegregated Witnessにより第三者によるtxidの変更はできなくなるが、分離された署名を含むwtxidを変更することで、BIP-152のCompact Blockを使った中継の効率を低下させることができる。

OP_CHECKMULTISIGOP_CHECKMULTISIGVERIFYにおける設計上の欠陥は、署名検証の後に余分なスタック要素("ダミー要素")を使用することにある。このダミー要素は特に検証されないので任意の値に置き換えることができる。このドキュメントではこの署名のmalleabilityを解消するための新しいルールを提案する。

仕様

ダミー要素を利用したmalleabilityを解消するため、新しいコンセンサスルールNULLDUMMYを追加する。このルールはダミー要素は必ず空のバイト配列でないといけないというもの。空のバイト配列以外の場合、スクリプトはすぐにfalseと判定する。NULLDUMMYのルールは、BIP-141で説明されているpre-segregatedスクリプトとpay-to-witness-script-hashのスクリプト内のOP_CHECKMULTISIGOP_CHECKMULTISIGVERIFYに適用される。

デプロイメント

このBIPはBIP-9のversion bitを使ってデプロイされる。その際使われるデプロイのパラメータはBIP-141、BIP-143と同じ"segwit"で、使用するbitは1。

mainnetでは、starttimeは2016年11月15日の深夜(Epociタイムは1479168000)でtimeoutは1年後。

testnetでは、starttimeは2016年05月01日の深夜(Epociタイムは1462060800)でtimeoutは1年後。

互換性

参照クライアントは最初から互換性のある署名を生成しており、NULLDUMMYルールは v0.10.0以降の参照クライアントの中継ポリシーとして施行された。そのため2015年8月以降、要件に違反するトランザクションはチェーンに追加されていない。

実際に使われている全てのscriptPubkeyタイプについて、非準拠の署名もちょっとした変換で準拠したものになるため、この要件によって機能が失われることは無い。特殊なスクリプトを設計する際は、ユーザはこの新しいルールに注意をはらう必要がある。

実装

参照クライアントの実装は以下のプルリクを参照。

github.com

謝辞

Peter ToddがNULLDUMMYの原作者で、この文書はPieter WuilleによってBIP-62から抽出された。

参考

techmedia-think.hatenablog.com

techmedia-think.hatenablog.com

Bitcoin Core自体は以前から対応してたみたいなので、特に新要素という感じでは無いかなー。

コントラクトの関数で例外を投げる

Solidityで作成するコントラクトの関数から例外を投げる方法について。

http://solidity.readthedocs.io/en/develop/control-structures.html#exceptions

コントラクトの関数内で手動で例外を投げたい場合throwを使う。例外が投げられると現在実行中の処理が停止し、状態や残高に関する全ての変更は取り消され元に戻る。

ちなみに発生した例外はキャッチすることはまだできない。

throwの例↓

pragma solidity ^0.4.0;

contract Sharer {
    function sendHalf(address addr) payable returns (uint balance) {
        if (!addr.send(msg.value / 2))
            throw; // Sharerへの転送を元に戻す
        return this.balance;
    }
}

Solidityで自動的に例外が発生するケース

以下のケースでは、自動的に例外が発生する

  • 配列にアクセスする際のインデックの値が大きすぎるかマイナスの値の場合
  • 固定長のbytesNに大きすぎるもしくはマイナスのインデックスでアクセスした場合
  • メッセージ呼び出しを使って関数を呼び出したが(gas不足や合致する関数が無い、その関数自体が例外を投げるなど)その関数が正常に終了しない場合。ただし、callsenddelegatecallといった低レベルの操作の場合は例外は発生せず、falseを返すことにより障害が発生したことを示す。
  • 新しいキーワードを使ってコントラクトを作成したがそのコントラクトが正しく作成されない場合(前述のような理由で)。
  • 0を使った除算、剰余を行った場合(5 / 0とか23 % 0とか)
  • 何のコードも含まれていないをターゲットにした外部関数呼び出しを実行した場合
  • payable修飾子が付いていない関数を介してetherを受け取った場合
  • コントラクトがpublicなアクセッサ関数を介してetherを受け取った場合

内部的には、Solidityは例外が発生するとinvalid jumpを実行することで、EVMが全ての変更を元に戻している。

実際に例外を発生させたトランザクションデバッグトレースを見ると

...
{
      depth: 1,
      error: "invalid jump destination (PUSH1) 2",
      gas: 2977807,
      gasCost: 8,
      memory: ["0000000000000000000000000bcfa0dd51b7be1081cd1b82b2e3cb17f7fa1d56", "a6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb51", "0000000000000000000000000000000000000000000000000000000000000060"],
      op: "JUMP",
      pc: 970,
      stack: ["000000000000000000000000000000000000000000000000000000004b5f4b1d", "00000000000000000000000000000000000000000000000000000000000000f1", "0000000000000000000000000000000000000000000000000000000000000000", "00000000000000000000000000000000000000000000000000000000000001f4", "a6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49", "61f469589cff8db864e34dcbe0959d5c4658d7fc34600773b9ffb8ae1321930e"],
      storage: {}
  }]
}

invalid jumpで終わってるのが分かる。