Develop with pleasure!

福岡でCloudとかBlockchainとか。

送信者−受信者間で簡単なコインジョインを行うBustapayプロトコルを定義したBIP-79

Bitcoinトランザクションはインプットに送信者のUTXOが、アウトプットに受信者のアドレスと送信者のお釣りがセットされる構成が一般的。ブロックチェーンの分析をする企業でよく採られるアプローチでは、こういうトランザクションの場合、インプットのUTXOは全て同じユーザーのものであるという仮定に基づいていて、その仮定の元、ブロックチェーン上のトランザクショングラフを解析し、ユーザーや企業の動向を分析しようとしている。

今回新しく提案されたBustapayプロトコルは、コインジョインといっても大量のインプット/アウトプットを集めてコインを撹拌しようという目的ではなく、トランザクションのインプットは全て同じユーザーのものという上記ブロックチェーンの分析の仮定を崩すための送信者−受信者間の簡単なコインジョインプロトコルを定義したもの↓

https://github.com/bitcoin/bips/blob/master/bip-0079.mediawiki

同様の目的で以前BlockstreamがPay to EndPoint(P2EP)というプロトコルを提案していた↓

techmedia-think.hatenablog.com

ただBlockstreamのP2EPの場合、受信者が所有するUTXOの情報に関するプライバシーを守るため、フルノードにアクセスしてダミーのUTXOををセットしたダミートランザクションを多数用意し、送信者との間で署名のやりとりをするなど、単一の決済プロトコルとしては重かった部分をBustapayでは削ぎ落としてシンプルになっている↓

Abstract

Bitcoinトランザクションを普通に作成すると、望ましくない情報が漏れることが多い。結果として、効果的なブロックチェーン分析技術により、有用な通貨に期待される重要な特性が危険にさらされる。

Bustapayは、支払いの送信者と受信者が、両者の直接的な利益を守るためいくつかの分析の仮定を破壊する方法で、両者がBitcoinトランザクションに協力して署名するための簡単で実用的なプロトコルだ。さらに、Bitcoin決済をする事業者にとって一定の問題であるUTXOセットのサイズを管理するのを手助けするため、受信者に重要な量の制御を与える方法を取る。

動機

最も強力なブロックチェーン分析のヒューリスティクスの1つは、トランザクションのインプットの全てが、(伝統的なコインジョイン独自の構造やマルチシグの使用など)別で知られている場合を除いて、単一のユーザーによって制御されているという仮定だ。他の手法(特にお釣り用のアウトプットの推測)と組み合わせることで、予想以上に正確な追跡が行われ、Bitcoinの参加者は容認できない個人的に、ビジネス上および財務上のリスクにされされ、Bitcoinの利便性と代替性を損ない、最終的に有用な通貨として機能する能力を損なうことになる。

しかし我々は、送信者と受信者のコインジョインでこれらの仮定を壊すことができる。コストのかからないスパイ/DoS攻撃を防ぐため、プロセスを開始する際に送信者に完全に有効な伝播準備のできたトランザクションの提供を求め、送信者がコインジョインを完了しない場合に受信者がそのトランザクションをブロードキャストできるようになっている。最も有望なことに、Bustapayのトランザクションは識別可能な構造を持たないため、任意のネットワーク分析は与えられたトランザクションがBustapayのトランザクションかどうか分からないため、分析のモデル全体の信頼性が損なわれ、Bitcoinのエコシステム全体に肯定的な外部性がもたらされる。

Bustapayのトランザクションは、受信者のUTXOの数を増やすことなく、実際に受信者に自分のUTXOセットを良いように管理する機会を与え、それは通常の支払い時の取引でのみ行われる。大規模なUTXOセットは、よく問題になり高価で、頻繁にプライバシーを破壊する統合を必要とする。Bustapayは、クラスタ化の仮定を破壊する以外に、送信量の難読化レイヤーも提供する。

この仕様では、導入を促進するにあたってシンプルさが1番重要であるとの前提で、複雑さと潜在的に有用な拡張を避けてきたことが注目に値する。

概要

Bustapayの支払いは送信者から受信者に対して行われる。

手順1:送信者は受信者に支払うBitcoinトランザクションを作成する

このトランザクションは全てのインプットに対してsegwitを使用し、完全に有効で署名済みである必要がある。トランザクションはネットワークに伝播できる状態であること(ただしこの段階ではブロードキャストされない)。

手順2:送信者は受信者にテンプレートトランザクションを送る

これはbustapay urlに対して、HTTPのPOSTリクエストで行われる。

手順3:受信者はトランザクションを処理し、部分的に署名されたコインジョインを返す

受信者はトランザクションを検証し、自身に支払うようにする。受信者は1つ以上のインプットを追加し(contributed inputs)、(オプションで)受信者自身に支払うアウトプットを増額する(一般的にcontributed inputsの合計分増額する)。こうして受信者が送信者に返す、部分的なトランザクションが作成される。これは送信者に自身のインプットに再署名してもらう際などに呼ばれる。

手順4:受信者は検証、再署名、Bitcoinネットワークへの伝播をする

受信者は部分的なトランザクションが正しくかつ悪意なく変更されていることを検証し(潜在的に信頼できない通信シャネルも使用できるため)、元の自分のインプットに最署名し最終的なトランザクションBitcoinネットワークにブロードキャストする。

手順5:受信者はBitcoinネットワーク上で最終トランザクションを確認する

受信者がネットワーク上で最終トランザクションを確認し(そして十分な承認数あるか)、送信された金額は通常の支払いのように見える(ネットワーク上で確認する金額と実際の送金額は異なるが)。タイムアウト後に受信者が最終トランザクションを確認できない場合は、支払いが確実に行われるように元のテンプレートトランザクションをブロードキャストし、強力なアンチDoSの仕組みを機能させる。

仕様

送信者にBustapayトランザクション送信先を知らせる標準的な方法は、BIP-21エンコードされたアドレスを使用する方法だ。この時パラメータとしてキー:bpu(BustaPayUrlの短縮表記)を使用する。アドレスの例は以下のようになる。

bitcoin:2NABbUr9yeRCp1oUCtVmgJF8HGRCo3ifpTT?bpu=https://bp.bustabit.com/submit

URLは短くしておくことを強く推奨する。

送信者がテンプレートトランザクションを作成するにあたっては、segwitインプットだけが使用できるという点を除いて、通常の送金トランザクションを作成するのと変わらない。送信者はBIP125(オプトインのFullRBFのシグナリング)と同様、通常よりやや積極的な手数料レートの使用を勧められるが、どちらも厳密には要求されない。

テンプレートトランザクションは、Bustapay URLにHTTP POSTでバイナリエンコードされたBODYとして受信者に送信する必要がある。

受信者はテンプレートトランザクションの検証で応答する。トランザクションに問題がある場合、もしくはトランザクションに不満がある場合(手数料が低すぎるなど)、HTTPレスポンスコード422を使って、拒否理由をユーザーに伝えるため人が読める形式の文字列と一緒にレスポンスを返す。

受信者がトランザクションを拒否した場合、それをネットワーク上に伝播してはならない。しかし、送信者は(どのエラーが出たかに関わらず)受信者がいつでもテンプレートトランザクションをブロードキャストできることを認識することが重要だ。クライアントはそのため受信者の意思に応じて行動する必要がある(再調整するか、トランザクションを伝播だけするか)。

Contributed Inputの選択

受信者はすくなくとも1つのインプット(=contributed input)をトランザクションに追加しなければならない。受信者のインプットがない場合、500 Internal Server Errorを使用する必要があり、クライアントは通常通りトランザクションを送信できる(もしくは後でやり直す)。一般的には、1つのcontributed inputのみを追加することを推奨するが、複数のインプットを追加するのが便利なケースもある。

受信者のUTXOセットを列挙させるために同じトランザクションのバリエーションを継続的に送信される攻撃を防ぐために、トランザクションに同じインプットがあった場合は、常に同じcontributed inputを返すのが重要だ。

可能であれば、できるだけ多くの他のトランザクションインプットと同じタイプのcontributed inputを選択する努力を受信者がすることが強く望まれる。

アウトプットの調整

トランザクションにインプットを加えた後、受信者はcontributed inputの合計量分、自身宛のアウトプットの合計(手数料を追加したい場合はその分を差し引いて)を追加するよう調整したい。しかし唯一の厳しい要件が、受信者は決してインプットを追加or削除してはならず、アウトプットの量を減らしてはならないという点だ。

部分的なトランザクションの返却

受信者は部分的なトランザクションの全てのcontributed inputに署名する必要がある。部分的なトランザクションはこの時点で有効なトランザクションではなく、送信者により再署名される必要があるため、元のテンプレートトランザクションから全てのwitnessを削除する。受信者は部分的なトランザクションをバイナリエンコードしたHTTPレスポンスとしてレスポンスコード200で返す。

送信者の検証

送信者は部分的なトランザクションについて重要な検証をしなければならない。送信者は以下を検証しなければならない。

最終トランザクションの作成

部分的なトランザクションの検証後、送信者は全てのトランザクションに署名し、最終トランザクションを作成する。送信者は受信者が送信者の別のインプットに署名するよう騙そうとしていないか慎重に注意することが重要だ。送信者はテンプレートトランザクションに存在するインプットにのみ署名する必要がある。送信者が慎重でない場合、受信者は送信者が実際に所有しているUTXOをcontribute inputに入れ、送信者が盲目的に全てに署名するという希望を持っているケースが考えられる。

トランザクションの公開

最終トランザクションが作成されると、送信者はそれを直接Bitcoinネットワークに送信する必要がある。送信者が妥当な時間内(例えば1分間)にトランザクションを送信しない場合、受信者は重要なアンチスパイ/アンチDoS戦略としてテンプレートトランザクションを公開すべきである。送信者はまた、受信者が不当にトランザクションの手数料を下げた(例:トランザクションサイズは増えたけど、手数料が十分でない)と判断した場合、最終トランザクションでなくテンプレートトランザクションを公開することもできる。最終トランザクションをネットワークに公開した後でも、最終トランザクションが承認されずテンプレートトランザクションの方が手数料が高い場合、(RBFのアドバンテージを取り)テンプレートトランザクションの公開を検討することができる。

実装の注意点

Bustapayの支払いを実装したいと思っている人のために、ここに受信者のためのいくつかの注意がある:

  • トランザクションがmempoolに適しているかどうかは、bitcoin core 0.17+でtestmempoolacceptを使うことで簡単にチェックできる。
  • TXIDによりトランザクションの追跡は不確か。正気を保つために全てのインプットが確実にsegwitであること。しかしトランザクションを検証しない限り、segwitはTXIDのmalleabilityを防げない。そのため、少なくともtestmempoolacceptを使っていることを確認すること。
  • Bustapayはあなたが入金アドレスを持っているかどうかを照会するのに悪意あるユーザーによって悪用される可能性がある。既に使用済みの入金アドレス宛に支払うBustapayトランザクションを受け入れないこと。
  • どのUTXOのユーザー、あなたのどのUTXOを明らかにしたのかマッピングを保持する必要がある。同じUTXOで照会があった場合、以前明らかにしたのと同じUTXOを返す必要がある。
  • BIP-69に基づいてトランザクションが既にソートされているかどうかチェックし、ソートされているのであればそのままにする。ソートされていない場合インプット/アウトプットをシャッフルする。
  • 参照実装はhttps://github.com/rhavar/bustapayで公開されており、bitcoin coreウォレットのRPC呼び出しのラッパーとして機能する。
  • 送信者は、受信者が送信者のコントロール下にあるインプットを追加し送信者が盲目的にそれに署名することを期待する攻撃に対して注意しなければならない。

後方互換

Bustapayはオプションのペイメントプロトコルで、後方互換性の問題はない。実際に、通常のトランザクション処理に加えてサポートできる。これは通常のBitcoinトランザクションに戻す必要があるため。

所感

P2EPでもBustapayでも上記仮定を崩すウォレットが1つでも利用可能になっているというのが重要で、実際にこれらのプロトコルが使われていないとしても(使われているかどうかはトランザクション見ても分からないので)、これらのプロトコルが使われている可能性があるということだけで上記の仮定を崩すことに繋がると思われる。

あと(P2PEも一緒だけど)副次的な効果として受信者のUTXOセットを減らせるというのも良いね。

2P-ECDSA: Fungible 2-of-2 MultiSigs for Today's Bitcoin at Scaling Bitcoin 2018

Scaling Bitcoin 2018 復習シリーズ。今回はLightning LabsのConner Fromknechtによる「2P-ECDSA: Fungible 2-of-2 MultiSigs for Today's Bitcoin」の発表↓

youtu.be

書き起こしは↓ scriptless-ecdsa

前半はLindellベースの2者間のECDSA署名プロトコル↓の解説と、後半はそれを実際にLightning Networkに適用する際の考慮事項についてまとめた発表になってる。

techmedia-think.hatenablog.com

あとところどころPedroの話が引用されてるけど、それはこのセッションの前に発表されたMulti-Hop Locksの話↓

techmedia-think.hatenablog.com

2P-ECDSAの歴史

  • 2016年にBlockstreamのAndrew PoelstraがSchnorrベースのScriptless Scriptを発表
    これにより共有データ(通常ペイメントハッシュ)をブロックチェーン上で明らかにすることなく、クロスチェーンでコインのスワップすることが可能になった。チェーン間でトランザクションがリンクされることもない。彼はその後、LNのハッシュプリイメージのチャレンジをこれで置き換える公正を提案する。
  • 2017年3月、PedroがMulti-Hop Locksの発表で言及したように、LNの各ホップで支払い情報がランダム化されることを保証するようになった。全ての決済は伝播された順に決済される。
    • これによりプライバシーが向上
    • Pedroの言うようなWormhole Attackへの防御になる
  • 2017年末、Yehuda Lindellは、効率的な2p-ECDSA署名プロトコルを発表した。このプロトコルは現在のBitcoinのノードと完全な下位互換があり、どのノードもこの署名検証を行える。また現在存在するP2PKHやP2WPKHで利用可能。
  • 2018年4月、PedroがLNのホップの無相関化のフレームワークを形式化したMulti Hop Locksをリリースした。これには2p-ECDSAに基づく、Scriptless Scriptの構築が含まれている。また経路内の各ホップはECDSAをベースにしてもいいし、Schnorrをベースにしてもいい。つまり経路内にECDSAとSchnorrが混在していても問題ない。そのため、現在のLNにECDSAベースのMulti Hop Locksを導入した後、後でSchnorrがBitcoinに導入されたら、LNのネットワークを断片化することなくスムーズに移行することができる。
  • 現在、いくつかの2p-ECDSAを研究している。

2p-ECDSAの概要

  • アリスは秘密鍵aと対応する公開鍵A = aGを持っている。
  • ボブは秘密鍵bと対応する公開鍵B = bGを持っている。
  • お互い公開鍵は共有しており、公開鍵Q = (ab)G = aB = bAを計算する。Qの対応する秘密鍵ab
    • 二人ともabのデータは知らない。 *二人で協力してQに対して有効なECDSA署名を生成する。

有効なECDSA署名を生成するにあたって、2つのアルゴリズムが必要になる。

  • KeyGen(オフライン)
    オンラインのSignプロトコルに参加するためのアリスとボブのセットアップ。コスト的に高価だが実行するのは1回だけ。
  • Sign(オンライン)
    効率的だが2回の往復が発生するオンライン署名プロトコルで、Qに対して有効なECDSA署名を生成する。

KeyGen

  1. 基本的にアリスとボブはそれぞれが持つ公開鍵と、その公開鍵の離散対数(秘密鍵)を知っていることの証明を交換する。これは標準的なShcnorr署名のデータを確認することで、その公開鍵に対応する秘密鍵を持っていることを確認できる。
  2. アリスはPaillier暗号用の鍵ペア(PSK, PPK)を生成する。またアリスがボブを騙すことが無いよう、鍵PPKが {N= p_1 * p_2} {p_1} {p_2}で構成されていることを証明するゼロ知識証明を提供する。
  3. アリスは公開鍵PPKで秘密鍵を暗号化し、暗号テキスト {c = Enc_{PPK}(a)}を作成する。
  4. アリスは(PPK, c)をボブに送る。
    • ボブは暗号テキストを受け取るだけで、元の値のサイズを知ることができないため、アリスはそれが小さいことを証明する必要がある。この証明のため、一緒に暗号テキストcの値が { 0 < Dec_{PSK}(c) < q}の範囲内であることをゼロ知識証明を提供する(qは曲線secp256k1の位数)。実際、暗号テキストは元の値よりはるかに大きなデータになる。
    • そしてcにアリスの秘密鍵を含まれていること =  {A = Dec_{PSK}(c) G} を証明する。
    • Lindellはこれを証明するのに新しいゼロ知識証明を発明しなければならず、2つの異なる暗号システムを混在させていた。
  5. ボブは、受信した証明全てを検証し、Q = bAを計算する。
  6. アリスはQ = aBを計算する。

KeyGenの結果、

  • アリスは2p-ECDSAの公開鍵Q = abG秘密鍵(a, PSK)を持つ。
  • ボブは2p-ECDSAの公開鍵Q = abG秘密鍵(b, c, PPK)を持つ。

Sgin

Paillier暗号の特性

なぜ、KeyGenでPaillierを使っているのか?というと、主な理由は、ECDSAではScnorrのように公開鍵や署名の加算ができない(公開鍵や署名が線形性を持っていないため)。このため非対話型の集約に適していない。ただ、Palliler暗号データは以下のような部分的な準同型の性質を持っている。

  • 加法準同型性(暗号化したデータ同士を加算できる)
    D(E(m1) * E(m2) mod N2) = m1 + m2 mod N
  • 暗号化したデータの元のメッセージに対してスカラ倍できるスカラ乗算
    D(E(m1)k mod N2) = k*m mod N

ボブはこれらをPPKを使って秘密鍵(PSK)の知識無しに計算することができる。

署名プロトコル

ECDSAの署名データは(r, s)。sは、 {\displaystyle s = \frac{H(m) + r * x}{k}}で、rはR = kGのx座標。

このECDSAの署名をアリスとボブは以下のステップで計算する。

  1. アリスとボブはそれぞれnonceを選択し、この公開鍵とそのnonceを持っていることのゼロ知識証明を共有する。アリス: {K_a = k_aG}、ボブ: {K_b = k_bG}
  2. ボブは暗号データ {\displaystyle c_1 = Enc_{PPK}(\frac{H(m)}{k_b})} {v = \frac{r * b}{k_b}}を計算する。ここでrは {R = k_a * k_b G}のx座標。
  3. ボブは {\displaystyle c' = c_1 * c^{v} \mod N^{2} = Enc_{PPK}(\frac{H(m) + r * a * b}{k_b})}を計算し、アリスに送る。
  4. アリスはそれを復号化した {s' = Dec_{PSK}(c')}を計算し、 {s'' = \frac{s'}{k_a} \mod q}
  5. 計算結果のs''は {\displaystyle s'' = \frac{H(m) + r * a * b}{k_a * k_b}}となる。
  6. 最後にアリスは {s = min(s'', q - s'' \mod q)}をセットし、(r, s)をSignアルゴリズムのアウトプットとする。

通常のECDSAプロトコルと比較すると、nonce kがアリスとボブのkに、秘密鍵もアリスとボブの秘密鍵に置き換わっている。

こうやって生成した署名はECDSA署名として機能する。最後のステップはLOW_Sルールの適用。

Scriptless Scriptの署名も同様のアプローチだが、シークレットの抽出で余分なラウンドを必要とする。詳細はPedroのペーパー参照。

ベンチマーク

  時間 割り当てられたメモリ 割り当て数 メッセージ数
KeyGen 1.07 秒 4.99 MB 13.31 K 7
Sign 28.66 ミリ秒 97 KB 746 4
Scriptless-Sign 29.40 ミリ秒 118 KB 1.12 K 5+1

KeyGenプロトコルの実際の暗号や複雑さを考慮するともっと遅くなると思われたが全体的に1.07秒で意外と速い結果になっている。おそらく1番遅い部分はgolangのbigintライブラリを使用している箇所で、これは時間が固定時間でなくメモリパフォーマンスに問題があることで有名だ。そのため、この時間は2倍 or 5倍 or 10倍に速くなる可能性がある。

署名は見ての通り30ミリ秒未満。結構大きい。オンラインプロトコルとしては上手く動作する。

Scriptless-Signには多くの最適化作業が残っている。

ベンチマークのコードは↓

github.com

LNへのデプロイの考慮事項

2P-ECDSA/Schnorrのデプロイの場合

Fundingアウトプット

現在は2-of-2のマルチシグにコインをロックしている部分。このコインをアンロックするには、協調してクローズする場合とCommitment Txをブロードキャストする場合の2ケースがあるが、いずれも2人の署名を必要とする。この部分をP2WPKHのロックスクリプトに置き換える。

通常の2-of-2のマルチシグ、Schnorr版、2P-ECDSA版、P2WPKHにおけるWitness Scriptとアンロックに必要なwitnessは以下のようになる。

  witness witness script
通常 OP_0 <aGの署名> <bGの署名> OP_2 <aGの公開鍵> <bGの公開鍵> OP_2 OP_CHECKMULTISIG
Schnorr <(a+b)Gの署名> <(a+b)Gの公開鍵> OP_CHECKSCHNORRSIG
2P-ECDSA <abGの署名> <abGの公開鍵> OP_CHECKSIG
P2WKH <aGの署名> <aGの公開鍵> OP_CHECKSIG

使用するスペースは約半分になる。2つの公開鍵は1つに、2つの署名は1つの署名になる。また、非広告チャネルのプライバシーを守るチャンスにもなる。LNのノードでチャネルを広告している場合は、プライバシーを諦めることになるが、プライベートチャネルの場合、Funding Txは通常のP2PKHもしくはP2WPKHのように見えるため、プライバシー上の大きなアドバンテージになる。特に、多くのモバイルデバイスやラップトップ上のノードが、ルーティングのためにノードを広告しないネットワークに移行するにつれて、大きな意味を持つ。また最後に、ゴシップレイヤーの帯域幅が少なくなる。署名が1つ少ないので一般的にネットワークの負荷状況が改善する。

HTLCアウトプット

非標準のHTLCスクリプトで(標準というのはBIP-199のこと?)、2-of-2のマルチシグを使っている。HTLCスクリプトには、offeredとreceivedの2種類のスクリプトがある。2つはほとんど似ているが、チャネル上のどの方向を向いているかが異なり、両方とも2-of-2のマルチシグを必要とする。スクリプト内のoffered-timeoutおよびreceived-successの条件でコインを使用する際に2-of-2のマルチシグを必要とし、その部分をよりシンプルになるよう置き換える。

例えば現在のReceived HTLCスクリプト

# revocation clause
OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocation pubkey))> OP_EQUAL
OP_IF
  OP_CHECKSIG
OP_ELSE
  <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
  OP_IF
    # success clause
    OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY 2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
  OP_ELSE
    # timeout clause
    OP_DROP <cltv_expire> OP_CLTV OP_DROP OP_CHECKSIG
  OP_ENDIF
OP_ENDIF

のようなスクリプトになっているが、2P-ECDSAを利用してより簡単なHTLCスクリプトにすることができる↓

OP_IF
  # revocation clause
  revocationpubkey
OP_ELSE
  OP_IF
    # timeout clause
    <cltv_expire> OP_CLTV OP_DROP <remote_delay_pubkey>
  OP_ELSE
    # success clause
    <2p_htlc_pubkey>
  OP_ENDIF
OP_ENDIF
OP_CHECKSIG

これにより、

  • スクリプトのサイズ自体が20%削減
  • スクリプトの可読性が向上
  • witnessのサイズ削減
    • successの場合のwitnessサイズは78%削減される。
    • revocationのケースでは30%ほど小さくなる。
    • タイムアウトの場合のwitnessサイズは同じまま。

Offered-HTLCスクリプトでも同様の改善が期待できる。

Scriptlessな2P-ECDSA/Schnorrのデプロイの場合

  • HTLCアウトプット
    Scriptlessな2P-ECDSA/Schnorrを使用すると、HTLCのスクリプトからペイメントハッシュを削除することができる。また拡張によってwitnessからプリイメージを削除できるので、witnessから52バイト削減できる。

スペースの削減という観点からは、手数料も安くなり、ブロックチェーンの負荷も軽減する。

双方向の2P-ECDSA

LNのチャネルは双方向なので、HTLCを更新したい場合、全ての更新とパラメータ(ペイメントハッシュや、タイムロック時間など)をチャネルの反対側に送信し、各チャネルはその更新を適用し、その後作成した署名のバッチを送信する。これを行うと片方の参加者のみが署名で終わる。

オンチェーン上に1つのFunding Outputがロックされていて、どちらの参加者もこれを使用するための署名を開始できる。どちらからでも更新を開始できるように、同じ鍵ペアの2つのインスタンスをセットアップし、それでFunding Output(もしくは同様のもの)のコインを使用するCommitment Txに署名できるようにする。

※ この部分の説明がイマイチよく分かってない。

オニオンパケット

BOLT 04で定義されている現在のonionパケットのデータ構造は以下のようになっている

サイズ 名称
1 version
33 public_key
20*65 hops_data
32 hmac

この内、hops_dataはメッセージ転送中に関連するホップによって使われる情報を含む20個の固定サイズのパケットで構成される。

hops_dataを構成する1つ1つのパケットのデータ構造は以下のようなもの↓

サイズ 名称
1 realm
32 per-hop
32 MAC
filter

このうち2P-ECDSA/Schnorrのデプロイにあたって変更するのはホップ毎のペイロードのデータ(per-hop)。

現在のper-hopペイロードは以下の32バイト

サイズ 名称
8 short_channel_id
8 amt_to_forward
4 outgoing_cltv_value
12 padding

新しいペイロードは以下のような構成になる。サイズは161バイトで、トータルのパケットサイズは3880バイトとなり元の1300バイトから約3倍のデータ増になる。

サイズ 名称
8 short_channel_id
8 amt_to_forward
4 outgoing_cltv_value
33 incoming_lock_pubkey
64 incoming_lock_dlog_pok
32 hop_lock_secret
12 padding

これらを構築して解読する際のボトルネックの大半は、一時鍵導出やBlindng Factorなどの非対称暗号操作で、ホップ数の増加と共に線形スケールする。そのため、パケットサイズの増加は構築/解読にほとんど影響を及ぼさない可能性がある。

2P-ECDSAとScriptless Scriptのサマリ

メリット

  • 現時点でデプロイ可能
  • スクリプトとwitnessが小さくなり、手数料が安くなる。
  • funding outputの匿名セットの向上によりオンチェーンプライバシーが向上
  • ホップ間の相関を無くすことでオフチェーンプライバシーが向上
    2P-ECDSAは、既存のトラフィックに混在し、Schnorrが始まると初期は匿名セットが小さくなる。ただ時間の経過と共にSchnorrに移行すると思われる。
  • リアルなProof-of-Payment(Invoice Tunneling)
  • この分野で多くの研究されている。

デメリット

  • 複雑である
    • 正しく実装する決して簡単ではない。
    • モバイルデバイスのリソース使用量はそれほど大きくないけど、プルーフを並行して処理するためのコアは少ない。
  • Commitment Txの更新の際により多くの情報の更新が必要で、より多くのラウンドトリップを必要とする(署名プロトコルをパイブライン化してラウンドトリップを削減することは可能かもしれない)。

The State of Atomic Swaps at Scaling Bitcoin 2018

Scaling Bitcoin 2018復習シリーズ。今回は10/7のThomas EizingerCOMITプロトコル開発してる人みたい)が発表した「The State of Atomic Swaps」の内容について見てみる↓

www.youtube.com

書き起こしは↓

http://diyhpl.us/wiki/transcripts/scalingbitcoin/tokyo-2018/atomic-swaps/

Atomic Swap

Atomic Swapはチェーン間で相手を信用することなく、アトミックにコインを交換するのに使用されるプロトコル。一般的なプロトコルとしてはHTLCが利用される。

アリスがBitcoinを持ち、ボブがLitecoinを持っている状態でアリスとボブはそれぞれBitcoinとLitecoinを交換したい場合、以下のような手順で相手を信用することなく各チェーンのコインをアトミックに交換できる。

  1. アリスはシークレットAをランダムに選択し、そのハッシュ値H(A)をボブに伝える。
  2. アリスは自分のBitcoinを以下のいずれかの条件でアンロック可能なスクリプト宛に送金するトランザクションを作成しBitcoinネットワークにブロードキャストする。
    • H(A)のプリイメージを提供すればボブの鍵でアンロック可能。
    • 2日後アリスの鍵でアンロック可能
  3. ボブは自分のLitecoinを以下のいずれかの条件でアンロック可能なスクリプト宛に送金するトランザクションを作成しLitecoinネットワークにブロードキャストする。
    • H(A)のプリイメージを提供すればアリスの鍵でアンロック可能。
    • 1日後ボブの鍵でアンロック可能
  4. アリスはシークレットAを使って3のボブのLitecoinを入手するトランザクションを作成しLitecoinネットワークにブロードキャストする。
  5. ボブはLitecoinのブロックチェーン上で4のトランザクションを確認することでシークレットAの値がわかる。
  6. ボブはシークレットAの値を使って2のアリスのBitcoinを入手するトランザクションを作成しBitcoinネットワークにブロードキャストする。

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

このようにシークレットとそのハッシュ値、タイムロックの仕組みを使って相手を信用することなくコインを交換するHTLCプロトコルが構成できる。

これを上記のようなBitcoin Scriptを使うことなく、署名技術を使って実現するScriptless Scriptというアプローチもある。

HTLCを使ったスワップの不公平

上記のようなHTLCを使ったAtomic Swapには1点、不公平な部分がある。

シークレットを生成した側のアリスには、シークレットを公開しない=HTLCをセットアップしたけど交換はしないというアドバンテージがある。この場合、タイムロックの期間が過ぎるとコインは自分の元に戻ってくる。コインの交換レートに合意して、両方のチェーン上でHTLCにコインをロックした後に、市場の取引レートが劇的に変わると、アリスはセットアップ時のレートで取引したくなくなり、HTLCを実行することなくタイムロックを待ち払い戻しを選択する可能性がある。この場合、取引相手のボブも払い戻し用のトランザクションを発行する。ボブにとっては、最初に合意したレートでのコインの交換ができず、セットアップと払い出し用の2つのトランザクションの手数料を負担しただけになる。

Atomic Swapプロトコルの修正

↑の不公平な状態を解消するため、Atomic Swapプロトコルにペナルティを導入する。重要なのは正しく罰することで、上記のケースではシークレットを公開しなかったアリスを罰することになる。アリスが正しい行動をしなかった場合=シークレットを公開しなかった場合に、アリスから担保を奪うようにする。

公正なAtomic Swap リビジョン1

リビジョン1で導入するのは、2つめのHTLCもしくは条件付き支払いを追加する。これはアリスがBitcoin上でボブに支払う代わりに、Litecoinのチェーン上に資金を償還した際に戻ってくる担保を作る。

担保設計

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

担保は条件付き支払いで、支払いパスが逆なだけで、他のHTLCで使われているのと同じプリイメージとハッシュを使用する。シークレットが明らかになった場合、その資金はアリスが手にし、タイムアウト後はボブが資金を入手できる。アリスがタイムロックを経過するまだ待っていたら、この担保はボブに渡る。これによりアリスがボブにプリイメージを明らかにするインセンティブができる。

ただ今度はアリスからボブへのトラストが発生する。アリスが2つめの担保用のHTLCトランザクションブロックチェーンにブロードキャスト後、ボブがHTLCトランザクションを発行せずアリスの担保用のHTLCのタイムアウトまで待つと、ボブは何もせずにアリスの担保分のコインを入手できてしまう。そのためこれは理想的なプロトコルではない。

公正なAtomic Swap リビジョン2

上記問題を解消するには、ボブがAtomic Swapの条件にコミットしていない状態にある時、アリスの担保は正しくアリスに戻ってくるようにする必要がある。そのためにアリスの担保とボブのHTLCの2つを以下のように1トランザクションで構成する。

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

  1. アリスはBitcoinをボブに支払うHTLCトランザクションを作成しブロードキャストする。
  2. アリスは自身の担保を入れたLitecoin用の担保トランザクションを構成する。アリスの担保をインプットとし、アウトプットの量は「ボブがアリスに支払うLitecoinの量」+ 「アリスの担保分のLitecoin」。
  3. 2で作成した担保トランザクションにアリスの署名を付与し、ボブに渡す。
  4. ボブは受け取った担保トランザクションのインプットに自分の資金をセットし、署名を付与しブロードキャストする。

f:id:techmedia-think:20181019101856p:plain
2,3で作成するトランザクションのインプット/アウトプット。ボブがインプットやお釣り用のアウトプットを追加できるようするため、アリスが作成する署名はSIGHASH_SINGLEを使って署名されると思われる。

トランザクションがLitecoinのネットワークにブロードキャストされると、アリスは担保があるためそれを手に入れるインセンティブが発生する。アリスがLitecoinを入手すると、ボブはBitcoinを入手するのに必要なシークレットが手に入る。

アリスがシークレットを明らかにせず、タイムアウト期間を過ぎると、HTLCの条件からボブは自分のLitecoinとアリスの担保分Litecoinを手にする。

3の後に4でボブがトランザクションをブロードキャストする前までであれば、アリスは担保トランザクションのインプットにセットしたUTXOを別のトランザクションで使用すればキャンセルが可能。なので3の後にボブから応答がない場合はアリスはAtomic Swapをキャンセルして担保金を取り戻せる。

プライバシー

上記のような公平なAtomic Swapプロトコルが考案したのでプライバシーについて考える。Atomic Swapをプライベートにする方法としてはScriptless Scriptを利用する方法がある。Scriptless Scriptはシークレットとハッシュ値を署名技術にエンコードできるのでチェーン上で他のトランザクションと区別がつかなくなる。

セットアップ

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

アリスとボブはそれぞれ鍵ペア(アリス:xa、ボブ:xb)を持ち、アリスだけ別にシークレット tを用意する。アリスはボブの秘密鍵とtを含むアドレスを計算する。このアドレスはボブの公開鍵とtGを加算した楕円曲線上の点をアドレスとしてエンコードすることで計算できる。ボブは資金を償還するのに2人の秘密鍵を必要とするマルチシグにLitecoinをロックする。この段階でアリスはボブの秘密鍵を知らないので、アリスがロックされた資金をアンロックできない。ボブはボブでシークレットtを知らないのでボブも資金をアンロックできない。ということでアリスもボブもこの段階ではどちらも資金をアンロックできない。

コインのアンロック

  1. ボブはLitecoin側のマルチシグにロックされているコインをアンロックするのに必要なAdaptor Signatureを作成する。これはコインをアンロックするのに必要な署名の一部で、これを完全な署名にするには、アリスが署名にtの値を含めるようにしないといけないよう設計されてる。
  2. アリスはボブの部分署名を完成させ、litecoinを入手するために必要な完全な署名を得る。これによりアリスのtは明らかになる。そしてボブがtを知り、tを自分の秘密鍵に加算してBitcoinをアンロックする署名を作成することができる。

Adaptor Signature使った署名のエンコード方法の詳細は触れられていないが、初期にAndrew Poelstraが発表したプロトコルは両方のチェーンでマルチシグにコインをロックするタイプだったので↓、そのアプローチとは異なるアプローチになってる。確かにtが分かればアンロック可能であれば、片側のロック条件はマルチシグじゃなくて良い。

techmedia-think.hatenablog.com

重要なのはこの仕組みがHTLCと同じ特性を持っているということ。アリスは最初に彼女だけが知っているシークレットを持ち、その値を明らかにすることなくボブの資金を手にすることはできない。また開始前にお互いにnLocktime付きのトランザクションに署名しておくことで、両者は資金を取り戻すことができる。

公正なAtomic Swapという意味でや↑のトランザクションは担保が無い。その点については特に言及されてないけど、担保を付与する場合は、おそらくLitecoin側のトランザクションにはアリスの担保分のインプットが追加され、マルチシグにロックされる。そしてnLocktime付きのトランザクションタイムアウト後、その取り分がボブに全部いくよう設計しておくようなパターンになると思われる。

Output Descriptorとscantxoutsetを使ってUTXOセットをスキャンする

Bitcoin Core 0.17.0からUTXOセットをスキャンするscantxoutsetというRPCが追加された。

Bitcoin Core :: scantxoutset (0.17.0 RPC)

今まではウォレット内に鍵を持つUTXOの情報はlistunspentなどで取ってこれてたけど、↑はウォレット機能とは別で、UTXOセットの中から指定した条件に合うUTXOを検索することができる。

この時、検索対象を指定する際に使用する記述子がOutput Descriptor↓

bitcoin/descriptors.md at 0.17 · bitcoin/bitcoin · GitHub

(訳は後述)

Output Descriptorを指定してscantxoutsetでUTXOを検索

scantxoutsetは以下のように2つの引数を取る。

$ bitcoin-cli scantxoutset <action> <scanobjects>
  • action
    実行するアクションを指定。
    • startでスキャンを開始
    • abortで現在のスキャンを打ち切り
    • statusで現在のスキャンの進捗を確認
  • scanobjects
    スキャン対象を配列で指定。各要素で指定可能なスキャン対象は今のところ以下の3つ
    • descriptor: 後述のOutput Descriptorの文字列を指定
    • ['desc': xxx, 'range': n]: (オプション)Output Descriptorとメタデータを含むオブジェクト。子HDチェーンをどこまで探索するかのインデックスを指定して検索したい場合(デフォルト:1000)

例えば、公開鍵0259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520のP2PKHのUTXOを探す場合、以下のようにscantxoutsetを実行してしばらく待つと結果が得られる↓

$ bitcoin-cli -testnet scantxoutset start "[  \"pkh(0259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520)\" ]" 
{
  "success": true,
  "searched_items": 20353944,
  "unspents": [
    {
      "txid": "1915bdd359feb0bb12bc4f6f1f0791b982def6936af420473f892bacd16db014",
      "vout": 1,
      "scriptPubKey": "76a9143b9722f91a2e50d913dadc3a6a8a88a58a7b859788ac",
      "amount": 0.10015556,
      "height": 1117751
    },
    {
      "txid": "e9dcfee7eb0715570cf8b3cc2149309ca4c0af3a4387f4eb92a4c04d746986ed",
      "vout": 1,
      "scriptPubKey": "76a9143b9722f91a2e50d913dadc3a6a8a88a58a7b859788ac",
      "amount": 0.46970645,
      "height": 1117751
    }
  ],
  "total_amount": 0.56986201
}

実行中は別のターミナルから↓を実行するとスキャンの進捗(%)が確認可能。

$ bitcoin-cli -testnet scantxoutset status
{
  "progress": 45
}

とこんな感じで、手軽にUTXOセットを検索できるようになったのは便利だ。

scanobjectsの指定方法は以下のようにOutput Descriptorを構成することで様々な検索が可能。

Output Descriptorの機能

以下、Output Descriptorの機能部分の訳↓

現在以下のOutput Descriptorをサポートしている。

  • pk関数を介して、Pay-to-pubkeyスクリプト(P2PK)をサポート
  • pkh関数を介して、Pay-to-pubkey-hashスクリプト(P2PKH)をサポート
  • wpkh関数を介して、Pay-to-witness-pubkey-hashスクリプト(P2WPK)をサポート
  • sh関数を介して、Pay-to-script-hashスクリプト(P2SH)をサポート
  • wsh関数を介して、Pay-to-witness-script-hashスクリプト(P2WSH)をサポート
  • multi関数を介して、マルチシグスクリプトをサポート
  • addr関数を介して、サポートされる任意のアドレスタイプ
  • raw関数を介して、RAWスクリプトのhex表記をサポート
  • hex表記の公開鍵(圧縮、非圧縮)または、BIP32拡張公開鍵と導出パス

サンプル

  • pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)は、P2PKアウトプットを表す。
  • pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)は、P2PKHアウトプットを表す。
  • wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)は、P2WPKHアウトプットを表す。
  • sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))は、P2SH-P2WPKHアウトプットを表す。
  • combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)は、P2PK、P2PKH、P2WPKH、P2SH-P2WPKHアウトプットを表す。
  • sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))は、P2SH-P2WSH-P2PKHアウトプットを表す。
  • multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)は、素の1-of-2のマルチシグを表す。
  • sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))は、P2SHの2-of-2のマルチシグを表す。
  • wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))はP2WSHの2-of-3のマルチシグを表す。
  • sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))はP2SH-P2WSHの1-of-3のマルチシグを表す
  • pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)は、指定されたxpubの公開鍵部分を使った単一のP2PKアウトプットを示す。
  • pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1'/2)は、指定されたxpubの1'/2の子鍵を使った単一のP2PKHアウトプットを示す。
  • wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/0/*))は、2つのHDチェーンの対応する導出パスの公開鍵を使った1-of-2のP2WSHマルチシグアウトプットを示す。

参照

記述子はいくつかのタイプの式で構成される。トップレベルの式は常にSCRIPT

SCRIPT式:

  • sh(SCRIPT)(トップレベルのみ): 引数を埋め込んだP2SH
  • wsh(SCRIPT)(別のwshの内部であってはならない): 引数を埋め込んだP2WSH
  • pk(KEY)(どこでも可): 与えられた公開鍵P2PKアウトプット
  • pkh(KEY)(どこでも可): 与えられた公開鍵のP2PKHアウトプット(公開鍵ハッシュしか知らない場合は代わりにaddrを使用)
  • wpkh(KEY)wshの内部以外): 与えられた圧縮公開鍵のP2WPKHアウトプット
  • combo(KEY)(トップレベルのみ): pk(KEY)pkh(KEY)のコレクションのエイリアス。もしkeyが圧縮されている場合、wpkh(KEY)sh(wpkh(KEY))も含まれる。
  • multi(k,KEY_1,KEY_2,...,KEY_n)(どこでも可): k-of-nのマルチシグスクリプト
  • addr(ADDR)(トップレベルのみ): ADDRが展開されるスクリプト
  • raw(HEX)(トップレベルのみ): HEXとして16進エンコードされたスクリプト

KEY式:

  • Hexエンコードされた公開鍵(02および03で始まる場合は66文字、04で始まる場合は130文字)。
    • wpkhwshの内部では、圧縮された公開鍵のみが許可される。
  • WIFエンコードされた秘密鍵は、対応する公開鍵の代わりに同じ意味で指定できる。(BIP 32で定義されている)xpubエンコードされた拡張公開鍵もしくは、xprvエンコードされた拡張秘密鍵
    • 0以上の/NUMの非強化鍵もしくは/NUM'強化鍵のBIP 32導出パスが続く。
    • オプションで、/*もしくは/*で直接的な子どもの非強化鍵、強化鍵のステップが続く。
    • 強化鍵の導出ステップの使用には、秘密鍵の提供が必要となる。
    • 'の代わりにh suffixを使って強化導出を表すことができる。

ADDR式はサポートされている任意のアドレス:

  • P2PKHアドレス(1...から始まるbase58フォーマット)。記述子内のP2PKHアドレスは、P2PKアウトプットに使用できないことに注意すること(代わりにpk関数を使うこと)。
  • P2SHアドレス(3...から始まるbase58フォーマット、BIP 13で定義)
  • Segwitアドレス(bc1...から始まるbech32フォーマット、BIP 173で定義)

説明

単一鍵のスクリプト

実際には、単一キーの構成が多く使われ、一般にP2PK、P2PKH、P2WPKHおよびP2SH-P2WPKHが含まれる。最適ではないがもっと多くの組み合わせが考えられるP2SH-P2PK, P2SH-P2PKH, P2WSH-P2PK, P2WSH-P2PKH, P2SH-P2WSH-P2PK, P2SH-P2WSH-P2PKH。

これを記述するために、これらを関数としてモデル化する。関数pk (P2PK)、pkh (P2PKH)、wpkh (P2WPKH)は、16進表記の公開鍵を入力として受け取り、対応するscriptPubKeyを返す。関数sh (P2SH) 、wsh (P2WSH)は入力としてスクリプトを受け取り、インプットをスクリプトとして埋め込んだP2SH及びP2WSHのアウトプットを記述したスクリプトを返す。関数名は簡潔にするためp2を省略している。

マルチシグ

いくつかのソフトウェアでは、BitcoinのOP_CHECKMULTISIG opcodeに基づいてマルチシグスクリプトを使用する。これをサポートするために、multi(k,key_1,key_2,...,key_n)関数を導入する。これは提供されたn個の公開鍵の内、任意のk個の署名が必要なk-of-nのマルチシグを表す。

BIP 32導出鍵とチェーン

最新のウォレットソフトウェアやハードウェアはBIP 32(HDキー)を利用して導出した鍵を使用する。期待される公開鍵を(一般にxpubと呼ばれる)拡張公開鍵と導出パスで構成される文字列で指定することを許可することで、これらを直接サポートする。導出パスは0以上の整数の順序で構成され、それぞれオプションで'もしくはhが付き、/文字で区切られる。文字列はオプションで、非強化導出、強化導出の全ての子鍵を参照するため/*または/*'(もしくは/*h)で終わることがある。

強化導出ステップを使って公開鍵を指定する場合、対応する秘密鍵にアクセスすることなくスクリプトを計算することはできない。

秘密鍵の包含

必要な秘密鍵を一緒にスクリプトの説明を伝えることはよく役に立つ。このため、公開鍵またはxpubがサポートされている場合、代わりにWIFフォーマットもしくはxprvを提供することができる。これは強化導出ステップや、秘密鍵の材料を含むウォレット記述子のダンプなどで秘密鍵が必要な場合に便利だ。

古いウォレットとの互換性

既存のBitcoin Coreウォレットで現在サポートされているスクリプトのセットを簡単に表現するため、入力として公開鍵を受け取り、その鍵でP2PK, P2PKH, P2WPKHおよび P2SH-P2WPHスクリプトを構成する便利な関数comboが提供される。公開鍵が圧縮されていない場合、P2PKおよびP2PKHのみを構成する。

lndで実装されているBIP-39に代わるシード管理方式「aezeed」

lndでBIP-39*1の欠点を修正するために実装されたシードの新しい管理方式「aezeed」について↓読んでみた。

https://github.com/lightningnetwork/lnd/tree/master/aezeed

2018年3月のSF Bitcoin Devsで@roasbeefがlnd v0.4-betaの紹介した際にも触れられてる。

www.youtube.com

docs.google.com

aezeedはBIP-39の以下の欠点を修正する:

  • バージョンの欠如
    バージョン情報がない場合、ウォレットがリカバリープロセス中にどのアドレスを再導出すべきか分からない場合がある(秘密鍵や公開鍵は導出できても、そこからどのアドレス:P2PKH、P2WPKH、P2SH-P2WSHを導出すべきか決定できない)。
  • ウォレットの誕生日の欠如
    ウォレットがいつできたかという情報がない場合、ウォレットが正しいユーザーのアドレスを全て確実に導出するために、チェーン内をどこまで見ていけば良いか分からない。

簡単に言うとこれらのデータをシードデータに含め、さらに暗号化したデータからmnemonicのワードリストを生成するようBIP-39を拡張した仕様になる。

具体的な仕様は↓

プレーンテキストのaezeedエンコーディング

aezeed方式のシードは、↑の欠点に対処するため以下の3つのフィールドを結合した値になる。

1バイトの内部バージョン || 2バイトのタイムスタンプ(ウォレットの誕生日) || 16バイトのエントロピー

内部バージョンは、ウォレットがウォレットの鍵を再導出する方法を知ることができるようにするためのフィールド。

2バイトのタイムスタンプはウォレットの誕生日を管理するためのフィールド。通常タイムスタンプは4バイトのデータ領域を必要とするが、このフィールドは2バイトでタイムスタンプを表現するためBitcoin Days Genesisで表される。これはBitcoinのジェネシスブロックのタイムスタンプからの日数を意味する。このフォーマットによりスペースを節約し、無駄な粒度を使わないようにすると。現在、これは2188年までの時間を表現することができる。

最後のエントロピーは、ウォレットのHDルートを導出するのに使用される。

BIP-39ではエントロピーからmnemonicを直接生成していたのに対し、aezeedではバージョンとタイムスタンプが加わった上記データからmnemonicが作られることになる。

aezeed 暗号化/復号化

BIP-39ではエントロピーにそのチェックサムを付与したデータを11bit毎に分割してmnemonicのワードリストを作成していたが、aezeedでは上記のプレーンテキストシードを暗号化してできた暗号テキスト(CipherSeed)からmnemonicのワードリストを生成する。

暗号化して生成される最終的なデータは以下の3つのフィールドを結合した値になる。

1バイトの外部バージョン || 暗号テキスト || 8バイトのチェックサム

1つめの外部バージョンは、暗号化方式を定義したもので、2つめの暗号テキストが実際にプレーンテキストシードを暗号化したデータ、最後がチェックサムで、以下のプロセスで生成される(暗号化プロセスでは、ユーザーが定義したパスフレーズが使われる。パスフレーズが指定されていない場合は、文字列「aezeed」が使われる)。

  1. 最初に、外部バージョンをバッファに追加する。外部バージョンは使用する暗号化方法について定義する。最初のバージョン(version 0)では、暗号化にscrypt(n=32768, r=8, p=1)とaezeedを使う。
  2. 次にscrypt(n=32768, r=8, p=1)を使って、暗号化に使用する強めの鍵を生成する。saltとして5バイト使って、32バイトの鍵を生成する(saltを使うのはレインボーテーブルを作成できなくするため)。
  3. 続いて暗号化プロセス。ナンスの誤用に耐性があるモダンなAEADであるaezを使用する。ここで重要な特性は、任意の入力長のブロック暗号であること。さらに設定可能なMACサイズを持っている点で、aezeed方式では64bitのチェックサムとして機能する8を使う。生成されたシードとAD of (version || salt)を使って暗号化する。
  4. 最後にBIP-39のデフォルトのワードリストを使って、この33バイトの暗号テキストをエンコードし、24個の英単語を生成する。

こうやってプレーンテキストシード(19バイト)から暗号シード(33バイト)が生成される。

aezeed暗号シードの特性

aezeed暗号シード方式にはいくつかの特性がある。

  • mnemonic自体が暗号テキストから生成されているので、mnemonicだけ分かってもコインを盗むのは難しく、BIP-39でパスフレーズセットしていないとmnemonicが分かればコインが盗まれるのとは対照的。
  • パスフレーズを変更することで暗号シードを変更できる。ユーザーがより強力なパスフレーズを望む場合は、古いパスフレーズで復号し、次に新しいパスフレーズで暗号化することができる。BIP-39の場合、ユーザーがパスフレーズを使ったとしても、マッピングが一方向であるため、既存のHDキーチェーンのパスフレーズを変更することはできない。
  • 暗号シードをアップブレード可能。外部バージョンがあるので、オフラインツールを使って古いパラメータを復号し、新しいパラメータを使って暗号化することができる。将来暗号を変更したり、scryptを変更したり、scryptのパラメータのみを変更するば、ユーザーはオフラインツールでシードを簡単にアップグレードできる。

ウォレットの誕生日やバージョン情報の管理は、Lightningに限らずオンチェーンでも課題よね。

*1:BIP-39はHDウォレットのマスターシードを人間が覚えやすい単語リスト(mnemonic word)に変換する仕様を定義したBIP