Develop with pleasure!

福岡でCloudとかBlockchainとか。

Bitcoinの新しいテストネットワーク「Signet」の仕様を定義したBIP-325

Bitcoin関連のテストを行うのに便利なtestnetだが、ブロックの生成間隔がまばら(30分くらい生成されなかったり、数秒で連続してブロックが生成されたり)だったり、巨大な再編成が起こったりとあまり安定していない。そのためテストになかなか使いづらくなっていた。このような問題を解消するため新しいタイプのテストネットワークがBIP-325として提案された↓

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

Signetでは既存のProof of Workの検証に加えて、ネットワーク起動時に予め設定するチャレンジと呼ばれるscriptPubkeyに対する有効な解答(scriptSig)が提供されるか検証するようになっている。ざっくり言うとPoAタイプのテストネット。

具体的には、ノード起動時に

$ ./bitcoind -signet_blockscript=<scriptPubkey(hexフォーマット)>

のように-signet_blockscriptオプションを使ってチャレンジのscriptPubkeyを指定する。これはP2PKHであってもいいし、k-of-nのマルチシグなどであってもいい。またこのブロックスクリプトによってSignetジェネシスブロックのデータも異なる。詳細はこちら

-signet_blockscriptオプションで起動されたチェーンは、ブロックをマイニングする際に有効なPoWの他、そのブロックのコインベーストランザクションのwitness commitment内にscriptPubkeyに対して有効な解答(scriptSig)を含める必要がある。有効なscriptSigが無い場合無効なブロックと判定される。この時、scriptSig内の署名および署名検証の際に使われるメッセージダイジェストは、ブロックから生成される(詳細は以下BIP参照)。

そのため、-signet_blockscriptに対応する有効な解答が作れるメンバー=対応する秘密鍵を保持し署名を付与できるメンバーしかブロックが作れなくなる。つまり予め決められたこれらのメンバーによりそのsignetのブロックは作られていくことになる。

以下、BIPの内容↓(仕組みの概要は書かれてるけど、Bitcoin Wikiの方がカスタムSignetの立て方など具体的に書いてて参考になるかも。)

2020/05/08時点の内容で更新

概要

複数の独立した参加者が関与する長期的なテストシナリオのため、ブロックの進行に対してProof of Workに加えて署名が使用される新しいタイプのテストネットワークにより、優れた調整と堅牢性を実現する。

動機

Testnetは実際のお金を危険に晒すこと無く新しいことを試すのに最適な場所だが、信頼性が低いことで有名だ。巨大なブロックの再編成や、マイニング中のブロック間に長いギャップが生じたり、また急に連続してブロックが作成されるバーストは、特に長期間に渡ってソフトウェアを実行する複数の独立した参加者が関与するソフトウェアの現実的なテストが実際に実行不可能となることを意味する。

新しいタイプのテストネットワークは、取引所などの組織による結合テストや、eltooやサイドチェーンペグなど次世代のLayer2プロトコルのテストにより適しいてる。目標は完全に信頼できることではなく、むしろ予測可能な非信頼性の量を持つことである。テストネットワークがmainnetと同じように動作する(つまり数千のブロックの再編成などはない)のと同時に、6ブロックの再編成などの予想される稀なイベントをトリガーしやすくする必要がある。Regtestはブロックの作成にコストがかからないため、複数の独立した参加者が関係する長期的シナリオには適していないため、どの参加者もテストネットワークを完全に制御できる。

仕様

新しいタイプのネットワーク「Signet」はチャレンジ(scriptPubkey)と呼ばれる追加のコンセンサスパラメータを受け取る。このチャレンジは単純なpubkey(P2PKHスタイル)、k-of-nのマルチシグもしくはその他の必要なスクリプトにすることができる。

コインベーストランザクションのwitness commitmentは、2つ目のコミットメント(署名/解答)を含むよう拡張される:

1-4 bytes - 以下の (x + 4) byteをプッシュする
4 bytes - Signetヘッダー (0xecc7daa2)
x bytes - 解答 (sigScript)

4バイトのSignetヘッダーで始まらないプッシュ操作は無視される。4バイトのSignetヘッダーを持つ複数のプッシュ操作は、最初のエントリーを除いて無視される。

チャレンジ内に含まれる全ての署名操作はSHA256d(modifiedBlockHash)、つまり以下のデータのダブルSHA256ダイジェストをsighashとして使用する:

サイズ 名前
Int32 4 nVersion
Uint256 32 hashPrevBlock
Uint256 32 modifiedMerkleRoot
Uint32 4 nTime
Uint32 4 nBits

modifiedMerkleRootハッシュは、コインベースのwitness commitmentに上記Signetの拡張が含まれない状態のブロックトランザクションのマークルルートを生成することで取得できる。これは、ブロックのマークルルートがSignet commitment内のマークルルートと異なることを意味する。これは署名されるメッセージ(この場合ブロック)に署名を含めることはできないためだ。署名とは別にブロック生成(マイニング)を簡単にするために、ブロックのnonce値はSignetの署名がコミットしないブロックの他の唯一のコンポーネントだ。Proof of Workを回していくのに、拡張用nonceは使用できない。これはそれを行うと署名が無効になるためだ。代わりに同じ(もしくは更新された)ブロックに再署名することで、Proof of Workの新しい計算スペースが得られる。

上記のコミットメントが見つかった場合、その解答が有効であれば、ブロックは完全に検証されたとみなされる。この検証はwitness commitmentの検証の前後に直接行うのを推奨する(両者の検証で必要なデータはほぼ同じであるため)。

ジェネシスブロックとメッセージスタート

全てのsignetネットワークにおいてジェネシスブロックは同じだが、メッセージスタートはシングルプッシュで定義されるチャレンジスクリプトのDouble-SHA256の先頭4バイト(以下参照)。

ジェネシスブロック

  • Time stamp: 1534313275
  • Nonce: 100123
  • Difficulty: 1e2adc28

この結果、ジェネシスブロックのハッシュは0000032d7f67af9ec7b7152aea0fe7c95b9804ff973265e252f245e0ae61799dで、ブロックのHex値は、

0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a3bc3735b28dc2a1e1b8701000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000

メッセージスタート

メッセージスタートはシングルプッシュで定義されるチャレンジスクリプトのDouble-SHA256の先頭4バイトとして定義される(つまりチャレンジスクリプトの長さがプレフィックスになる)。

  • チャレンジスクリプト = 512103ad5e0edad18cb1f0fc0d28a3d4f1f3e445640337489abb10404f2d1e086be43051ae
  • Sha256d(長さ || チャレンジスクリプト) = sha256d(25512103ad...51ae) = 7ec653a59b1912f9db10da2c461ed827d48f9404d5ef0346a6c94aadd4203646
  • 先頭4バイト = the message start = 7ec653a5

互換性

この仕様は、既存のソフトウェアがすぐにSignetを使用できるという意味で後方互換性がある。

Signet用のネットワークパラメータ(マジックナンバーなど)を追加するだけで、クライアントは変更を加えることなく任意のsignetネットワークに接続して使用できる。ブロックヘッダーには有効なproof of workがあるため、クライアンはブロックが「おそらく」有効であることを簡単に確認できる。

ただし、特定のsignetネットワークでクライアントが受け入れたブロックは誰でもマイニングができる。ただし、これらのブロックには必要な署名が含まれていないため、完全な検証を行うノードはすぐにそのブロックを拒否する。そのため、クライアントはコインベーストランザクション内のブロックの署名を検証するか、信頼できるピアに接続する必要がある。

他のソフトウェアは本番環境で使用しないブロックの署名検証コードを追加する必要はない。これはネットワークが可能な限りmainnetのように動作することが目標である非実稼働のテスト目的に適している。

参照実装

https://github.com/bitcoin/bitcoin/pull/16411

2者間の非対話型CoinJoinプロトコルSNICKER

2者間で、同期や対話なくCoinJoinを作成する新しいプロトコルSNICKER(Simple Non-Interactive Coinjoin with Keys for Encryption Reused)が提案されている↓

https://gist.github.com/AdamISZ/2c13fb5819bd469ca318156e2cf25d79

提案者のブログポストは↓

joinmarket.me

CoinJoinとは?

CoinJoinプロトコルはUTXOセットを難読化/ミキシングするのに有名な手法で、古くはShared CoinやDarkWallet、最近だとJoinMarketやWasabi Wallet、Samouraiなどで利用されている。

もともと2013年にGregory Maxwellによって提案されたプロトコルで管理者などの中間者無しでミキシングを行うためのプロトコル。ミキシングの参加者は送金額を満たすインプットとアウトプット(必要に応じてお釣り用のアウトプット)を提供する。この時アウトプットのコインの量は(お釣りを除いて)全て同じ額になる。各自が持ち寄った情報からミキシングトランザクションを作成する。各参加者が所有するインプットへの署名は、このトランザクションに対して行われるので、自身のコインが第三者に盗まれるということはない。理想的なCoinJoinのトランザクションはインプットとアウトプットの金種(コインの量)が全て等しいトランザクションになる。

CoinJoinの課題

↑に理想的なトランザクションと書いたけど、CoinJoinの難点は、参加者間の調整が必要だという点で、参加者が強力な匿名性を確保したい場合は特に難しくなる。この課題のソリューションの1つとして、CoinJoinトランザクションを作成する調整用のサーバーを使用する方法もあるが、ユーザーのプライバシーを低下させるトレードオフになり、攻撃に対して障害点となりやすい。

またそんなサーバがいない場合、シビル攻撃によりプロトコルの妨害をしたり、参加者のプライバシーを損なうような攻撃を効果的に実行できる。

SNICKERプロトコルとは?

SNICKERは、上記の参加者間の調整や同期を不要にすることで、この調整のトレードオフを解消するCoinJoinプロトコル。必要なのは片方のユーザーが暗号化されたデータをブロードキャストし、それをもう1方が受信するだけ。この受信はラジオや衛星を介して行われる可能性もあるため、何が起きたかを全て知ることは物理的に不可能となると(カジュアルにそこまでやることができればだと思うが)。

具体的には、提案者がブロックチェーンの情報のみを使って、別の参加者のアウトプットを推定し、部分的に署名されたCoinJoinトランザクションを作成する。そしてそのトランザクションを受信者が複合できる形で暗号化して何らかの方法でブロードキャスト(公開)する。作成するCoinJoinトランザクションは、ブロックチェーン上で確認できる公開鍵から(アドレスの再利用かもしくはトランザクションインプットの中にある公開鍵から抽出して)作られる。

※ ただし、SNICKERのCoinJoinモデルは2者間のみのCoinJoinであるという意味で非常に制限されたものになる。

現在SNICKERプロトコルにはバージョン0とバージョン1の2つのプロトコルが提案されている。

SNICKER Protocol version 0

バージョン0はアドレスの再利用が前提のプロトコルになる。

提案者がやること

提案者は、再利用されるアドレスのセットを特定する。具体的には、現在未使用のUTXOがあり、それと同じアドレスが以前に少なくとも1回使用済みであるアドレス。このアドレスA毎に、以前そのアドレスのコインが使用されたトランザクションを見つけて、そのアドレスの公開鍵 {P_A}を見つける。続いて、以下の手順を実行する。

  1. 提案者自身のUTXOを1つ見つける。このUTXOのコインの量は↑の {P_A}のUTXOのコインの量と2-of-3のトランザクションの手数料を加えた額以上のコインの量を持っている必要がある。
  2. 1で見つけた提案者のUTXOの公開鍵をQとするとQと {P_A}でECDHしたtweek  {c = ECDH(Q, P_A)}を計算する。
  3. Aの新しいアドレスを公開鍵 {P_A + cG}を使って計算する。
  4. 以下のCoinJoinトランザクションを構築する。
    • Input: {P_A}のUTXOと提案者自身のQのUTXOの2つ。
    • Output: {P_A}から導出した新しい公開鍵 {P_A + cG}から生成したアウトプットと、提案者の新しいアウトプットアドレス2つ(O1、O2)。O1は {P_A + cG}が受け取るのと同額を受け取り、O2はお釣り用のアウトプット。
  5. 提案者は自身のインプットに署名をする。
  6. 提案者は自身の署名を加えたトランザクションとtweakの値cシリアライズし、それをメッセージMとし、 {P_A}を使ってECIESの暗号化スキーム {ECIES-E(P_A, M)}で暗号化したProposalを作成する。
  7. 作成したProposalを掲示板に公開する。

受信者がやること

受信者は再利用したアドレスを記録しておく必要がある。このアドレスを上記と同じようにAと呼ぶ。

受信者は定期的に掲示板をチェックし、暗号化されたBlobを全てダウンロードする。各Blobについて(このバイナリBlobをoとする)、ECIESの復号スキーム {ECIES-D(p_A, o)}を実行し、復号を試みる(ここで {p_A}はアドレスAの秘密鍵)。復号化したデータについて以下をチェックする。

  • 先頭7バイトがSNICKERマジックバイト0x534e49434b4552かどうか。
  • versionが0x00かどうか

チェックをパスするとシリアライズされた値cと部分的に署名されたトランザクションが復号されているので、続いて以下の処理を行う。

  1. トランザクションアウトプットの1つが自身のアドレスAから導出した{P_A + cG}から作られたアドレスであること。
  2. 1の秘密鍵鍵をウォレットにインポートする。
  3. BIP-174ベースの部分的に署名されたトランザクションについて悪意ある脆弱性が無いかチェックする。具体的には、
    • トランザクションのバージョンが02で、ロックタイムは0、インプットのsequenceが0xffffffffであるかチェックする。
    • 署名が1つのインプットに対して有効なものであるか検証する。このインプットと署名は提案者のもので受信者のものではない。
    • 署名されていないインプットの所有権を自身が持っているか確認する。
    • {P_A + cG}を再構築できるか検証する。
    • 自身の使用する金額と新たに受信する金額を確認する。
    • 手数料が現在のネットワークで適切な額が確認する。ブロードキャスト後手数料を上げる場合、RBFは使えないので、CPFPを使うことになる。
  4. 全てのチェックが問題なければ、受信者はPSBTフォーマットのトランザクションに自身の署名を追加し、有効なBitcoinトランザクションを完成させ、Bitcoinネットワークにブロードキャストする。

なお、受信者が↑のトランザクションを受け取った時点で、別の参加者によってConJoinが行われるなどで既に提案者のUTXOが使用済みである可能性はある。そのようなケースがあったとしても受信者が資金を失うようなことは無い。

SNICKER Protocol version 1

バージョン1がほとんどバージョン0と同じプロセスだが、アドレスを再利用しないという点が異なる。

バージョン0では提案者はブロックチェーンをスキャンしてアドレスの再利用を検知して、そこから未使用のUTXOの公開鍵を検知してるため、アドレスの再利用が前提だった(ほとんどのトランザクションは公開鍵のハッシュのコインをロックしてるので、公開鍵が分かるのはそれを使用する時)。しかしBitcoinトランザクションのscriptSigには他にも公開鍵として利用可能なデータがある。

その1つがECDSA署名の署名データだ。ECDSAの署名値(r, s)においてrは署名用に選択したnonce kから導出した点R = kGのx座標だ。このRを↑の{P_A}の代わりに使うことでアドレスの再利用を必要としないSNICKERプロトコルが可能になる。(ただし、そのTxの署名者であることの推定が必要になるが)

基本的にBitcoinの代表的なウォレットであれば、署名生成の際にランダムに選択するnonce kの値はRFC6979に従って署名対象のトランザクションデータから決定論的に導出される*1。つまり、署名をしたウォレットであればkの復元が可能である。

まとめ

CoinJoinを行う際はどうしても対話型になるが、SNICKERは参加者の対話なく2者間のCoinJoinを行う新しいプロトコルの提案だ。

基本的にはCoinJoinを行いたい提案者が適当なUTXO(基本的にはアドレスが再利用されているUTXO)を見つけ、自身のUTXOを加え2者間のCoinJoinトランザクションを構築する。その際、相手のアドレスは、相手の公開鍵と自身の秘密鍵を使ってECDHを実行し共有シークレットを生成し、そのシークレットから生成した点を相手の公開鍵に加算して新たな公開鍵=アドレスとする。自身の署名を加えたら相手の公開鍵を利用してトランザクションデータと共有シークレットをECIESで暗号化して、そのデータを公開する。

公開したデータを発見した受信者は、内容を確認して問題なければ自身の署名を付与してブロードキャストする。

提案者と受信者の間には直接的な通信は発生しない。ブロックチェーンからスキャンしたUTXOに対応する受信者がSNICKER利用者でマッチするかという課題や受信者にインポートが必要な鍵の管理についての課題はある。

AMPを実現する3つのプロトコル

Lightning Networkにおいて、単一の経路ではキャパシティが不足し送金額に満たないが、複数の経路を使えば送金額を満たす場合に、複数の経路を使った支払いをアトミックに行うプロトコルがAtomic Multi-Path Payments(AMP)だ。

AMPのプロトコルとしては、以下の3つのプロトコルが提案されている。

プロトコルがどのようなものかというと

OG AMP

OG AMPはとOlaoluwa Osuntokun(@roasbeef)とConner Fromknechtが発表したAMP。

通常のLNの支払いでは受信者がプリイメージを生成し、そのハッシュをInvoiceで通知するが、OG AMPの場合、送信がベースプリイメージを生成する。AMPで使用する経路の数をnとすると、送信者はn個のシェアを生成し、それらの排他的論理和を計算してベースプリイメージを生成する。

ベースプリイメージ = s1 ^ s2 ^ .... ^ sn

続いて、各 {i \in }[1..n]をシーケンス番号とし、iとベースプリイメージを連結しハッシュした部分的なプリイメージ {r_i = H(BP || i)}を計算する。そしてi番目の経路の支払いで使用するハッシュ {h_i = H(r_i)}を計算する。

送信者は {h_i = H(r_i)}を各経路の支払いに使用し、支払いをスタートするが、受信者のHop Payloadの中にのみ {(ID, n, s_i)}を暗号化しておく(IDはAMPの支払いを識別するためのランダムな値)。

受信者は、上記AMPの部分的な支払いを複数の経路で受信すると、同じIDのデータをn個集める。n個揃うとその排他的論理和は取ればベースプリイメージBPが復元できる。BPが復元できたら、各部分的な支払いを受け取るために必要な {h_i}のプリイメージ {r_i}を計算することができ、この値を使って全経路のコインを受け取る。

各経路でベースプリイメージのシェアを配布することで、全経路分のシェアが集まらないとコインが受け取れないという意味で、複数の経路を使った支払いのアトミック性を担保する。

というのがOG AMPだが、元々LNではプリイメージを受信者が生成して、支払いが完了したらそのプリイメージが送信者に分かるという仕組みになっており、送信者にとってプリイメージは送信が正常に行われたことの暗号学的な証明になっていた。ところがOG AMPの場合、送信者がプリイメージを生成するため、この証明の仕組みが機能しなくなるという欠点がある。個人間送金などであれば問題ないけど企業間決済などのエンタープライズユースケースにおいてはこの証明を必要とするパターンも考えられる。

Base AMP

OG AMPの支払いの証明を課題を解決するため、Base AMPでは各支払いの経路で全て同じpayment_hashを利用する。

受信者がbasic_mppフィールドを含むオニオンパケットを受信した場合、受信ノードはBase AMPである可能性があると認識する。受信ノードは全ての経路の支払いが揃うまでプリイメージを明らかにしない。そのために、basic_mppのオニオンパケットを受け取った場合は他の経路のHTLCを受け取るまで60秒待つ。これは、悪意ある中間ノードが複数の経路にいた場合、片方の経路で入手したプリイメージを使って、別の経路の資金を入手するような可能性を阻止するため。OG AMPでは経路毎のpayment_hashが異なるのでこれを防げる。payment_hashが同じHTLCを集め、その送金額の合計がtotal_msatで指定された額になったら、同じタイミングでコインを受領する。

Base AMPの場合、全ての経路の支払いの受領タイミングのコントロールをアトミックに制限することはできず受信ノードの振る舞いに依存するが、送信者にとってのプリイメージの証明特性(Proof-of-Payment)は維持される。

High AMP

High AMPは、OG AMPの各支払経路で異なるハッシュを使うことによる複数のHTLCの相関関係を隠す効果と、Base AMPのProof of Payment特性の両方を実現するために研究されているプロトコル

どうやって実現するかというと、既存のHTLCのハッシュのプリイメージの交換部分を、楕円曲線上の点と(その秘密鍵である)スカラーに置き換える。

受信者は、秘密鍵となるスカラー {k_{par}}をランダムに選択し、対応する公開鍵=楕円曲線上の点 {K_{par} = k_{par}G}を計算し、Invoiceに記載する。この {K_{par}}を親公開鍵、 {k_{par}}を親秘密鍵とする。

秘密鍵 {k_{par}}に対して、各iの子鍵は {k_i = H(i || k_{par}G) + k_{par}}として導出できる。そして親公開鍵 {K_{par} = k_{par}G}に対して、各iの子公開鍵は {K_i = H(i || K_{par})G + K_{par} = k_iG}として計算できる(子鍵は部分的な支払いの経路分作られる)。

送信者は支払いに使用する経路の数分、 {K_i}を導出し、導出した各 {K_i}について対応する子秘密鍵 {k_i}の開示を必要とする条件付き支払いのHTLCを構成する。この場合HTLCの非タイムロック部分はScriptless Scriptで構成される(受取人がT = t * Gを提供し、有効な署名を作るとtが明らかになるパターン)。

こうして送信者→受信者への複数の経路の支払いが作成される。受信者は部分的な支払いを受信しても、すぐにそれを受け取るインセンティブはまだない。なぜなら、部分的な支払いの内、1つでも {k_i}を明らかにしてしまうと、送信者が {k_{par} = k_i - H(i || K_{par})}を計算することで親秘密鍵を入手し、それをもってProof of Paymentとして機能してしまうからだ。

そのため受取人はすべての経路の支払いを受信してから、コインの請求を行う。それによりすべての子秘密鍵が明らかになり、それのいずれかを使って送信者は親秘密鍵 {k_{par}}を抽出できる。

送信者は親秘密鍵 {k_{par}}を使ってデジタル署名を作成することでProof of Paymentを証明でき、かつ各部分的な支払いには異なる {K_i}が使われるため、複数の経路感の相関関係を隠すことも可能になる。

ただHigh AMPについてはBitcoinにSchnorr署名が実装される必要がある(まぁ最近の2P-ECDSAの研究から、ECDSAでも可能っぽいけど)。

というのが、OG AMP、Base AMP、High AMPの違い。BOLTのwikiを見る感じだとBOLT 1.1ではBaseとOGの両方が一応サポートされるっぽい。

TumbleBitに代わる新しいPayment Channel Hubプロトコル「Anonymous Atomic Locks」

Scaling Bitcoin 2019復習シリーズ第二弾は、Payment Channelの最近の研究といえばこの人、Pedro Moreno-Sanchezの「A2L: Anonymous Atomic Locks for Scalability and Interoperability in Payment Channel Hubs」

BitcoinのScalingソリューションとしてはペイメントチャネル技術にHTLCベースのマルチホップ決済を組み合わせたLightning Networkが有名だが、ベースとなるペイメントチャネル技術はLN固有の技術ではない。LNに可変サイズのマルチホップ決済はサポートしないが、誰かがハブ(タンブラー)となることでユーザー間のオフチェーン決済をサポートするPayment Channel Hubというコンセプトもある。Lightning Networkに比べてネットワーク情報を維持するコストは無いが、反面Hubという集中化モデルになる。

また、Lightning Networkはオニオンルーティングにより支払い経路の中継者は前後のノード情報しか分からないため、実際の支払い経路がA→B→Cという経路であったとしてもBからはAが実際の送信元で、Cが実際の受信者であるか知ることはない。反面Hub型のモデルはハブとなるBがAとCがそれぞれ送信元、受信者であることが分かるので、これを秘匿するための別の仕組みが必要になる。Payment Channel Hub(PCH)には以下の特性が求められる。

  • アトミック性
    A→B→Cの決済において、A→Bの支払いが行われた場合からなずB→Cの支払いも行われることが保証されること。
  • リンク不可能性
    A→B→Cの決済において、Aの送金先がCであることをBがリンクできないことが保証されること。
  • 値のプライバシー
    送金額のプライバシーが保証されること。
  • Fungibility
    各コインはそれぞれ区別されるべきではなく、PCHのタンブラーを使用したことが(スクリプトなどから)分からないことが保証されること。
  • インターオペラビリティ
    タンブラーを介して異なる暗号通貨で送金ができること。A→BはBTC、B→CはEtherのような。

PCHの提案としては、これらの要素を全て満たすような技術は現状存在しない。1番有力なPCHの実装はTumbleBitだが、コインがHTLCをサポートするコインに限定されるという意味でインターオペラビリティが限定的で、TunbleBitを利用したことがトランザクションからわかることからFungibilityにも課題が残る(Fungibilityに関してはTaprootの技術なんかと組み合わせると影響は限定できると思う)。

今回の提案では、上記の特性をできるだけカバーする新しいPCHのプロトコルAnonymous Atomic Locks(A2L)が提案されている。

Anonymous Atomic Locks(A2L)

A2LプロトコルはTumbleBitに結構似ていて、Promise(ロック)フェーズとPayment(リリース)フェーズの2つのフェーズで構成される。

Adaptor Signatureの利用

A2LではHTLCに依存しないよう、暗号チャレンジの入手とコインの引き換えはAdaptor Signature*1を使いスクリプトレスに実現するようになっている。Adaptor Signatureを利用したA→タンブラー→Bの基本的な支払いは流れは:

  1. タンブラーがまずシークレットskを生成する。
  2. アリスとタンブラー、タンブラーとボブの間でskの値が分かれば完全な署名が完成する=skを除外しないと正しい署名にならないHalf-signatureを作成する2P-Adaptorロックを完成させる。この時、2つのAdaptor Signatureには同じskの情報が使われ、これによりアトミック性が保証される。
  3. アリスとタンブラーの支払いが行われれば、その署名値からskを計算し、タンブラーからボブの署名を完成させることができる。

2つのAdaptor Signatureを利用することで、支払いのアトミック性が得られるが、課題になるのはタンブラーによるリンク不可能性だ。このリンク不可能性を保証するため、アリスとボブはそれぞれ秘匿されているskのデータをランダム値を使ってマスキングする。

プロトコルの概要

具体的にどのようにランダム性を利用しているのかプロトコルについて見てみよう。A2LプロトコルはSchnorr署名を利用する構成と、ECDSA署名を利用する2つの構成が提案されている。

Schnorr署名を利用した構成

署名データに線形特性のあるSchnorr署名の方が構成としてはシンプルになる。A→タンブラー→Bの支払いを行うステップは以下のようになる(アリスとタンブラー、ボブとタンブラーは既にそれぞれPayment Channelを開いていると仮定する)。

Promiseフェーズ

まず最初に暗号チャレンジの解(↓の手順でタンブラーが生成するランダム値α)と交換にタンブラーはボブにコインを支払う(αが分かればボブはこの送金Txの署名を完成させられる)Promiseフェーズを実行する。

  1. アリスとタンブラー、タンブラーとボブの間で共有公開鍵をそれぞれセットアップする。ボブとタンブラー間についてはボブの秘密鍵 {sk_b}、タンブラーの秘密鍵 {sk_{t1}}とすると共有公開鍵は {pk_{bt} = (sk_b + sk_{t1})G}。タンブラーとアリス間についてはタンブラーの秘密鍵 {sk_{t2}}とし、アリスの秘密鍵 {sk_a}、共有公開鍵は {pk_{at} = (sk_a + sk_{t2})G}。(このセットアップには分散鍵生成プロトコルを使用する)
  2. 続いてタンブラーは、ボブとのペイメントチャネルを新しい状態に更新する(ボブに資金を送金する)ため、新しいコミットメントトランザクションを作成し、内容についてボブと同意する。トランザクションに同意するだけで、この時点では未署名。
  3. タンブラーはランダムな値αを選択し、Paillier暗号で暗号化する(暗号化に使用するのはタンブラーのPaillier暗号用の公開鍵)cα =  {Enc(pk_T, α)}。このcαとA = αGをボブに送信する。※ 正確にはAが秘密の値αを使って生成されたことであるゼロ知識証明も一緒に送信され、ボブにより検証される。
  4. 続いてタンブラーとボブはコイントスプロトコルを実行して、 {R' = k'_1G + k'_2G + A}に同意する。R'はタンブラーが持つ秘密の値 {(α, k'_2)}とボブが持つ {k'_1}の値を使って構成されるが、それぞれ相手の秘密の値は知らない。
  5. 続いてタンブラーは、2で作成したコミットメントTxのタンブラー側の署名をR'を使って計算する。コミットメントTxから生成するSchnorr署名にしようするハッシュ値をe'とする。ただし、この時αを含めない。そのため計算した値は {k'_2 + e' \cdot x_2}となる。これをボブに送る。
  6. ボブは送られた値に対して、自身の持つ値を使って {s' = k'_1 + k'_2 + e \cdot (sk_{t1} + sk_b)}を計算する。署名値は(R', s')となるがこれはまだ不完全で、完全に有効な署名にするためには(R', s' + α)を算出する必要があるが、この段階ではボブはαを知らないので署名を完成させることはできない。
  7. 続いてボブは、ランダムな値βを選択する。3でタンブラーから受け取った暗号化されたcαおよびタンブラーの公開鍵 {pk_T}を使い、 {c'α = cα \cdot Enc(pk_T, β) = Enc(pk_T, α + β)} *2 および {A' = A + βG = (α + β)G}を計算しランダム化する。
  8. 最後にランダム化した値をアリスの送信する。

以上がPromise(ロック)フェーズで、図にまとめると↓な感じ。

f:id:techmedia-think:20191007170526j:plain
A2L-Promiseフェーズ

Paymentフェーズ

続いて、アリスとタンブラー間で、ボブから受け取ったランダム化された値を使ってアリスがタンブラーから秘密の値αの解をボブにしか分からない形で購入する。

  1. アリスはまずランダムな値τを選択し、ボブから受け取ったランダム化された値を再度ランダム化し、 {c''α = c'α \cdot Enc(pk_t, τ) = Enc(pk_t, α + β + τ)}およびA'' = A' + τG = (α + β + τ)Gを得る。
  2. アリスとタンブラーはPromiseプロトコルで行われたようにコイントスプロトコルを実行し、 {R'' = k'_3G(= R_a) + k'_4G(= R_{ta}) + A''}に同意する。R''はタンブラーが持つ秘密の値 {(α, k'_4)}とアリスが持つ {k'_3}の値を使って構成されるが、それぞれ相手の秘密の値は知らない。
  3. アリスはランダム化したc''αをタンブラーに送信する。
  4. タンブラーは自身がもつPaillier暗号の秘密鍵でc''aを解凍し、γ = (α + β + τ)を入手する。
  5. アリスはタンブラーとのチャネルを使ってタンブラーに送金する新しいCommitment Tx Aを作成し、内容についてタンブラーと同意する。
  6. タンブラーはCommitment Tx Aについてタンブラー側の署名 {k_4 + e'' \cdot sk_{t2}}を計算し、アリスに送る(e''はコミットメントTxから生成するSchnorr署名にしようするハッシュ値)。
  7. アリスはタンブラーから貰った署名値にGを乗算して、 {R_{ta} + e'' \cdot P_{t2}}と一致するか検証する。
  8. アリスはCommitment Tx Aのアリス側の署名を計算し、タンブラーから受け取った署名と合算した {s'' = k_3 + k_4 + e'' \cdot (sk_a + sk_{t2})}を計算しタンブラーに送信する。
  9. タンブラーはアリスから受け取ったs''とγを使って有効な署名データ {s = s'' + γ = k_3 + k_4 + e'' \cdot (sk_a + sk_{t2}) + γ}を完成させ、アリスに公開する。
  10. アリスはタンブラーが公開したsからs''を引いてγを入手する {s - s'' = γ = α + β + τ}
  11. アリスはγから自身が付与したランダム値τを差し引いたα' = γ - τ = α + βを計算しボブに送信する。
  12. ボブはアリスから受信したα'から自身が付与したランダム値βを差し引き、αを入手する。
  13. ボブはαが分かったので、Promiseフェーズで作成したCommitment Tx Bの有効な署名 {s = k_1 + k_2 + e' \cdot (sk_{t1} + sk_b) + α}を完成させ、タンブラーから資金を得る。

以上がPayment(リリース)フェーズで、図にまとめると↓な感じ。

f:id:techmedia-think:20191007180117j:plain
A2L-Paymentフェーズ

ECDSA署名を利用した構成

結構なボリュームになったので、詳細なプロトコルに関してはホワイトペーパー参照。

基本的なプロトコルの流れはSchnorrの構成と同じで、ただECDSA署名にはSchnorrのような署名データの線形特性が無いので、鍵生成の方式が変わっており、Lindellのプロトコル秘密鍵もPallier暗号で渡す仕組みになっている。

所感

A2Lプロトコルは、

  • 暗号チャレンジに解凍と引き換えにコインを渡すという仕組みでアトミック性を担保し
  • 暗号チャレンジをする際に、送信者と受信者で解答に対するランダム化を行うことでタンブラーに送金元と送金先のリンク不可能性を担保し
  • 暗号チャレンジの解答をAdaptor Signatureの要素として組み込みスクリプトレスにすることで、相互運用性およびFungibilityを担保する

Payment Channel Hubの提案だ。ただ値のプライバシーについては十分ではない。

基本的な構造はTumbleBitと同じだが、Adaptor Signature使ってスクリプトレスにしてる部分が追加の特性になっている。ただ、リンク不可能性やFungibilityに関しては、TumbleBitと同様利用者が少なければリンクできたり、異なる金種を用いるとリンクできたりする可能性はそのまま残るように思える。

*1:参考動画:https://goblockchain.network/2018/12/adaptor_signature/

*2:Paillier暗号の準同型特性により暗号化したままデータの計算が可能になる。

Watchtowerと連携したLightning Networkウォレットのバックアッププロトコル「Açai」

Scaling Bitcoin 2019復習シリーズ第一弾は、「Açai: a backup protocol for Lightning Network wallets」

通常のオンチェーン決済をするウォレットであれば、BIP-32やBIP-39などの技術を使って、ニーモニックワードさえ記録しておけば、そこからマスターシードを復元し、ウォレット内の取引で使用する秘密鍵を復元することができるが、LNのようなオフチェーン決済では、このような事が難しく、ウォレットの障害などでデータが破損、欠落してしまうとLNのチャネル内の資金を失う可能性がある。このような問題に対処するためのLNウォレットのバックアッププロトコルの提案が↑の内容。

Watchtower

バックアッププロトコルに関連するプロダクトの1つがWatchtower。

LNを利用した支払いでは、取引相手が不正をした場合(相手に有利な古い状態のチャネルをオンチェーンのブロードキャストするなど)、不正をした相手の資金を含むチャネルの全資金をペナルティとして没収するトランザクション(Justice Tx)をオンチェーンにブロードキャストすることで対抗する。ただ、これを正しく実行するためには常にオンチェーンの状態を監視しておく必要があるが、スマートフォンなど常にオンラインにあるか分からないようなデヴァイスにとっては難しいこともある。そこで監視とJustice Txのブロードキャストをアウトソースするための仕組みがWatchtowerだ。

Watchtowerの仕組み

アリスがボブとチャネルを開いいる場合、Watchtowerは以下のように動作する。

アリスはボブとのチャネルのコミットメントが更新される度に、最新のチャネル状態をWatchtowerに通知する。具体的には、以下のようにHintとBlobをWatchtowerに送信する。

  1. アリスとボブ間のチャネル状態を変更(新しいコミットメントに署名)
  2. アリスは新しいコミットメントTxのtxidの先頭16バイトをHintとする。
  3. アリスはtxidの後半16バイトを共通鍵としてコミットメント自体も暗号化したBlobを作成する。
  4. アリスはHintとBlobをWatchtowerに送信する。

ボブがアリスを裏切って古い状態のコミットメントをブロードキャストすると、

  1. Watchtowerはアリスから送られてきたHintとメモリプール内のトランザクションのtxidの先頭16バイトを比較し、合致するトランザクションが無いかチェックする。
  2. 合致するトランザクションがあれば、そのtxidがHintと一緒に送られてきたBlobを復号するための鍵になるので、その鍵を使ってBlobを復号する。
  3. WatchtowerはそのBlobのデータを使ってJustice Txを完成させ、ブロードキャストする。

という手順で監視およびJustice Txのブロードキャストをする。

※ Watchtowerに関しては、インセンティブの問題や、いつまでHintやBlobを保持するのか?、ちゃんとJustice Txをブロードキャストされるか?、プライバシーなど課題もまだあり、さまざまな提案が出ている。

Açaiプロトコル

識別子 Açaiプロトコルでは、Watchtowerをチャネルの監視だけでなく、バックアップサービスとして利用するプロトコルで以下の機能がベースになっている。

  • Watchtower
  • Eltoo
  • BIP-32, 44, 39

そしてWatchtowerののtxid、Hint、Blobの仕組みをバックアップの仕組みでも同様に使用する(監視とAçaiによるバックアップの仕組みを区別するのにAçaiバックアップに使用する際の表記をtxidç, Hintç, Blobçと表記する)。

Watchtowerへのバックアップの送信

チャネルを更新する都度、アリスは新しいチャネル情報(従来のHintとBlob)と新しいバックアップ( Hintçと Blobç)をWatchtowerに送信する。

  1. blobçを復号化したり、識別に使用するtxidçを生成するため*1、その準備としてまずアリスは現在のブロック高を導出変数として利用しBIP-44ベースで公開鍵を生成する(公開鍵= m’/108’/0(mainnet)/(アカウント番号)’/0/現在のブロック高)。108'はBIP-44の任意のpurpose番号で、アカウント番号は使用しているウォレットアカウントの番号。
  2. 続いて算出した公開鍵を使ってtxidçnを計算する。アリスは同じブロック高で多くの支払いを実行する可能性があるので、同じブロック高において新しいAçaiバックアップの状態:txidç_0, txidç_1, txidç_2, txidç_3を列挙する必要がある。同じブロック高ではその識別ができないので、同じブロック高内で行われた変更は、前のtxidçの値をハッシュした値として計算する。つまり、txidçnは前のtxidç_n-1のハッシュ値として計算される。3回支払いしているケースでは、txidç_1= SHA256(txidç_0)txidç_2= SHA256(txidç_1)txidç_3= SHA256(txidç_2)のようになる。
  3. txidç_nの前半16バイトをヒントHintç_n= txidç_n[:16]とし、後半16バイトを使ってBlobを暗号化するencrypt Blobç_n= Enc(dataç_n,txidç_n[16:])。ここで暗号化する対象のデータdataç_nはアリスが持つ全チャネルの最新のコミットメントTxのTXIDのリストだ。
  4. 新しいチャネル状態と一緒にバックアップ情報としてHintçnとBlobçnをWatchtowerに送信する。

ポイント

上記のように、ブロック高を変数に鍵導出した公開鍵からtxidçを計算し、その先頭16バイトをユーザーがWatchtowerへ問い合わせする際の識別子(Hintç)として使用し、後半16バイトをBlobçを暗号化/復号化する際の共通鍵として使用する。

Watchtowerへのバックアップの要求

アリスは誤ってデータを失った場合、Watchtowerに対してバックアップを要求できる。また実際にデータを失っていない場合も、Watchtowerにバックアップを要求することで、Watchtowerがちゃんと約束したサービスを提供しているか確認できる。

  1. アリスはノードに現在のブロック高を確認する。
  2. ブロック高とシードを使って決定論的に公開鍵を計算する。 address= m/108’/0(mainnet)’/(アカウント番号’)/0/現在のブロック高
  3. 公開鍵を使ってtxidç_0 = 2SHA256(公開鍵)を計算し、その先頭16バイトをヒントhintç_0=txidç_0[:16]とする。
  4. Watchtowerに対してhintç_0を問い合わせる。
    • Watchtowerがhintç0を知っている場合、同じブロック内に複数のトランザクションがある場合はhintç1も含まれるので、アリスのウォレットはtxidç_1= SHA256(txidç_0)を計算し、Watchtowerにhintç1が含まれるか尋ねる。Watchtowerがhintç1を知っていればさらにtxidç2の計算hintç2の確認と繰り返す。Watchtowerがhintç2を知らない場合、txidç1に最新のチャネル情報が含まれていると想定できるので、アリスはblolbç_1を復号しtxidのリストを抽出する。
    • Watchtowerがhintç_0を知らない場合、アリスには現在のブロック高でのトランザクションは存在しないと想定できるので、ブロック高をデクリメントして新しい公開鍵を公開鍵= m/108’/0(mainnet)’/(アカウント番号)’/0/(現在のブロック高 - 1)を計算する。計算した公開鍵に対して再度ヒントを計算し、上記のチェックを行う。
  5. アリスのウォレットがhintçnを見つけたら、Watchtowerに対して、対応するBlobçnを送るよう要求する。Blobç_nはdataçとtxidç[:16]で構成され、dataç= [アリスが開いているチャネルの最新の状態のtxidのリスト(ボブとそれ以外ともチャネルを開いている場合、その最新のtxidのリスト)]
  6. アリスはdataçからボブとのチャネルの最新のtxid_Blobを復元できる。その値を使って、Hint_Bob = txid_Blobの先頭16バイトを計算し、対応するblob_BlobをWatchtowerに問い合わせる。アリスはtxid_Blobの後半16バイトを使ってblob_Blobのデータを復号し、ボブとのチャネルを復元することができる。
  7. この時点でアリスは自分のデータがWatchtowerに保存されているものと一致するかチェックしたり、データを復元することができる。

ポイント

上記のように、現在のブロック高からバックアップに使用した公開鍵を計算し、そのHintçをWatchtowerに照会し、見つかるまでブロック高をデクリメントしながら繰り返し、最新のバックアップを見つける。最新のバックアップには、アリスの全チャネルの最新のコミットメントTxのTXIDのリストが含まれているので、今度はこのTXIDの先頭16バイトをHintとしてWatchtowerにチャネル状態を問い合わせ、対応するBlobを取得し、TXIDの後半16バイトを使ってそのBlobを復号する。基本的にEltooであれば最新のコミットメントTxが復元できればいいので、復号化したBlobから最新のコミットメントTxを復元できればいい。

Açaiプロトコルの前提条件

  • LNウォレットがWatchtowerとデータ交換する機能がWatchtower側に実装されていること。
  • Watchtowerにはサービスのための追加のストレージや帯域幅を考慮し、経済的に実行可能なトラストレスは支払いの形態が必要であること。
  • Watchtowerに保存されるAçaiデータは削除されたり、置換/改竄されることが無いこと。
  • Açaiプロトコルを使用するユーザーはどのWatchtowerを使用したか分かっていること。

所感

既存のWatchtowerの監視とJustice Txの保持の仕組みと上手く連携して、鍵導出の仕組みを組み合わせた興味深いバックアッププロトコル。まだインセンティブやデータの保持期間など課題はまだあるけど。ちなみにAçaiは植物のアサイーからの名付けとのこと。

*1:txidçと書いてるけどバックアップに用いるtxidçの実体はトランザクションの識別子ではなく、これから計算する公開鍵を使った識別子でしかない