読者です 読者をやめる 読者になる 読者になる

Develop with pleasure!

福岡でCloudとかBlockchainとか。

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のスクリプトプログラマーがちゃんと設計してねというスタンスみたい。