Develop with pleasure!

福岡でCloudとかBlockchainとか。

楕円曲線を使ったゼロ知識証明

↓のゼロ知識証明の解説記事に掲載されている楕円曲線を使ったゼロ知識証明をbitcoin-rubyを使って検証してみた。

www.atmarkit.co.jp

検証手順は↑のの記事の通りで、

証明者(アリス)と検証者がいて、楕円曲線上の点Pとアリスの秘密鍵aがあるとき、その公開鍵は

Q(公開鍵) = aP

となり、PQは検証者に共有されている。この状況でアリスは確実に秘密鍵aを持っていることをaを開示することなく、検証者に証明する。

まず、アリスはランダムな値rを選択肢、 R = rPb = H(P, Q, R)を計算する。Hはハッシュ関数で、今回はBitcoinハッシュ関数hash160を使用。
続いてc = r + abを計算し、検証者に(R, c)を渡す。

検証者は共有された値PQRcを使ってアリスがaを持っていることを確認する。
確認方法b = H(P, Q, R)X = cPY = R + bQを計算し、XYが等しければアリスはaを持っていると判断する。

どうしてXYが等しければアリスはaを知っていると判断できるのかというと、X = cP = (r + ab)Pは、秘密鍵abを乗算し、ランダムに選択した秘密鍵rを加算した値になる。そしてYは公開鍵Qbを乗算し、秘密鍵rの公開鍵Rを乗算した値になる。秘密鍵に対してrを加算した場合、その楕円曲線上の点は公開鍵QRを加算したものと等しくなるという楕円曲線の特性と、仮にaを知らない場合その計算式が成り立つようなRや(ハッシュ関数によって生成された)cをアリスが導出することは非常に難しいことから、X = Yが等しい場合アリスはaを知っていると判断することができる。

検証コードは↓

Rubyで楕円曲線の鍵共有(ECDH)と加算

Bitcoinで使われる楕円曲線の鍵について、bitcoin-rubyを使って鍵交換(ECDH)と楕円曲線の加算をしてみる。

鍵共有(ECDH)

アリスとボブが持つ鍵を使って共通鍵を生成してみる。

アリスとボブはそれぞれお互いの公開鍵を知っており、秘密鍵をそれぞれab楕円曲線のベースポイントPとすると、

アリスの公開鍵は、

Q(a) = a*P

ボブの公開鍵は

Q(b) = b*P

となる。

この前提でアリスとボブがお互いに相手の公開鍵を自分の秘密鍵を使ってスカラー倍算する。

アリスは

a * Q(b) = ab*P

ボブは、

b * Q(a) = ba*P

楕円曲線上でab*P=ba*Pとなるため、お互いが計算した値は同じ点を指す。この時アリスはaをボブはbを相手に公開していないので、安全に共通鍵を生成できる。

この楕円曲線を使ったDH鍵共有をbitcoin-rubyを使って書くと↓のようになる。

実際の鍵共有はffiでOpenSSLの機能を使っており、dh_compute_keyの中身はOpenSSLのC実装になる。

楕円曲線の加算

Bitcoinの取引の量を秘匿するConfidential Transactionや、Pay-to-Contractプロトコルなど楕円曲線の加法特性を利用した暗号トリックを使うケースが増えており、そんな楕円曲線の加算を行うRubyのコードを書いてみる。

アリスのBitcoinの公開鍵と秘密鍵に対して、ランダムに選択した値rを別々に加算する。公開鍵にr加算してできた公開鍵と、秘密鍵rを加算してできた秘密鍵から生成した公開鍵が同じ値になることが確認できる↓

こちらの楕円曲線の加算の実装もOpenSSLを利用。

ECDHも加算も楕円曲線の演算は全てOpenSSL側で実装されているので、Rubyはあくまでラッパーかな。Secp256k1を使った実装もあるけど、そっちも実装はlibsecp256k1なので、いずれにせよPure Rubyの実装があるわけではない。

ペアリングベース暗号のライブラリをRubyから使う

ペアリングベースの暗号の基礎的な数学的演算をCで実装したlibpbcというライブラリがある。

PBC Library - Pairing-Based Cryptography - About

このライブラリをJavaに移植したJPBCというライブラリもある。

JPBC - Java Pairing-Based Cryptography Library : Introduction

が、Ruby版の移植ライブラリはまだ無いようなので↓(Pythonはあるっぽい)

PBC Library - Pairing-Based Cryptography - Downloads

SWIGを使ったRubyバインディングを使ってみる↓

github.com

SWIGは、CやC++で書かれたプログラムをJavascriptPerlPHPPythonRubyなどのいろんな言語で使えるようにする開発ツール。

ビルド

Ubuntu 16.04 LTS上でビルドする。

libpbcのインストール

$  wget https://crypto.stanford.edu/pbc/files/pbc-0.5.14.tar.gz
$ tar xvfz pbc-0.5.14.tar.gz
$ cd pbc-0.5.14
$ ./configure
$ make
$ sudo make install

swigのインストール

$ sudo apt-get install swig

cmake

ビルドにはcmakeの3.7.0以上が必要なので、入ってない場合はインストールする。Ubuntu 16.04の場合aptのリポジトリのcmakeの最新版は3.5.1なので↓

cmake package : Ubuntu

aptではなく、ソースを取得してビルドした。

$ wget https://cmake.org/files/v3.7/cmake-3.7.1.tar.gz
$ tar xvfz cmake-3.7.1.tar.gz
$ cd cmake-3.7.1
$ ./bootstrap
$ make
$ sudo make install

/usr/local/binにcmakeがインストールされる。

Rubyライブラリの生成

以下のステップでRubyのライブラリを生成する。

$ git clone git@github.com:xu-cheng/pbc-bindings.git
$ cd pbc-bindings
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_RUBY=ON
$ make

makeが終わるとbuild/swig/ruby直下にpbc.soとpbcRUBY_wrap.cxxが生成されてる。

実行

Rubyから実行してみる。pbc.soがあるbuild/swig/ruby直下でirbする。

2.3.0 :001 > $:.unshift File.join(File.dirname(__FILE__), ".")
...
2.3.0 :002 > require 'pbc'
 => true
2.3.0 :003 > Pbc::Pairing.new
 => #<Pbc::Pairing:0x000000011592c0 @__swigtype__="_p_std__shared_ptrT_pbc__Pairing_t">

と、ちゃんとバインディングできてるっぽい。

あとは、まずlibpbcを理解してからだなー。

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