BitVMやZeroSyncの開発者であるRobin Linusが先日公開したBitStreamのペーパー↓
https://robinlinus.com/bitstream.pdf
分散型のコンテンツホスティングネットワークに対して(最近ユーザーが増えてるNostrとか)、持続可能なインセンティブを持たせるため、コンテンツのダウンロードに対して支払いを求めるPay to Downloadというアプローチを提案している。
コンテンツプロバイダーは、以下のプロトコルで支払いと引き換えにコンテンツをユーザーに提供する。
- プロバイダーは、検証可能な形でコンテンツを暗号化し、ユーザーに送信する。
- ユーザーは、提供された暗号化データが復号可能かどうか検証する。
- ユーザーは、HTLCを使ってプロバイダーから復号鍵を購入する。
- プロバイダーがHTLCの資金を入手すると、そのトランザクションからユーザーは復号鍵を得られる。つまりHTLCのプリイメージが復号鍵。
- ユーザーは得られた復号鍵を使ってコンテンツを復号する。
復号鍵の入手と支払いがHTLCによってアトミックに行われるけど、HTLCのプリイメージで本当に復号できるのかをどう担保しているのか見ていく。
File IDの導出
まず、コンテンツは固定サイズのチャンクに分割され、各チャンクのハッシュをリーフノードとしたマークルツリーを構成する↓
このマークルツリーのルートハッシュが、このコンテンツを識別するための一意のFile IDになる。
ユーザーは、このFile IDを指定してプロバイダーに対象のコンテンツを要求する。
暗号化
ユーザーからコンテンツを要求されたプロバイダーは、コンテンツを暗号化する。
コンテンツは以下のようにXORを使用した単純なワンタイムパッドで暗号化される(なので各チャンクは↓のプリイメージのハッシュ値と同じ固定長である必要がある)。
- まず暗号化に使用するランダムな値を選択する。これがHTLCのプリイメージになる。
- コンテンツの各チャンク毎(チャンクのインデックスをiとする)に、プリイメージとインデックスのハッシュ値とでビット単位のXOR演算を行うことで各チャンクを暗号化する。つまり暗号化されたチャンクは。
暗号化された各チャンクは、再度XOR演算を行うことで()、復号できる。
暗号化されたチャンクとインデックスにプリイメージが揃えば復号できるため、HTLCに設定するペイメントハッシュは
ペイメントハッシュ = H(プリイメージ)
となる。
Encrypted IDの導出
続いて、暗号化された各チャンクと、元の各チャンクのハッシュ値のペアをリーフノードとしてマークルツリーを構成する↓
このマークルツリーのルートハッシュがEncrypted ID。
コンテンツの購入
そして、プロバイダーは以下のデータをユーザーに送信する。
- 各チャンクのハッシュ値()
- 各チャンクを暗号化したデータ()
- ペイメントハッシュを含むLNインボイス
Claim = Encrypted ID || ペイメントハッシュ
とし、このClaimをメッセージとしたデジタル署名
データを受信したユーザーは以下を行う。
- 各チャンクのハッシュ値()からマークルツリーを構成し、そのルートハッシュがFile IDと一致するか検証する
- 各チャンクの暗号化データとハッシュ値からマークルツリーを構成しルートハッシュ(Encrypted ID)を計算する。
- Claimのデータをメッセージとして、プロバイダーが提供したデジタル署名が有効か検証する。
- LNインボイスを確認
検証をクリアしたら、ユーザーはLNインボイスに対して支払いを行う。
コンテンツの復号
プロバイダーがプリイメージを使用してLN支払いを受け取ると、ユーザーはプリイメージを入手できるので、プリイメージを使用して、暗号化されたチャンクを復号する。
そして、復号した各チャンクのハッシュ値が、元々受け取っていたと一致するか検証する。
Fraud Proof
復号したデータの検証が失敗した場合、ユーザーにとって以下のデータがプロバイダーの不正を証明するFraud Proofになる。
- Claimをメッセージとしたプロバイダーの署名
- ペイメントハッシュのプリイメージ
- 正しく復号できなかったチャンクのペアがEncrypted IDのマークルツリーに含まれていることを証明するマークルプルーフ
ここで証明しているのは、プロバイダーがコミットしたコンテンツ(チャンク)のハッシュ値が、復号したチャンクのハッシュ値と一致しないこと。
Bond Contract
支払いはしたけれど復号できなかった場合に、Fraud Proofを使ってプロバイダーにペナルティを与えるのがBond Contractの役割。Bond Contractは、
- Claimに対するプロバイダーの署名の検証
- ペイメントハッシュとプリイメージの検証
- 復号後のハッシュが一致しなかったのペアのEncrypted IDツリーに対するマークルプルーフの検証
- 暗号化されたチャンクを復号(XOR演算)してと一致しないことの検証
を行い、これをパスするとプロバイダーにペナルティを与える。
実際にLiquidのtestnetでデモされたトランザクションが↓
プロバイダーがデポジットした資金をOP_RETURN
で焼却している。具体的なコントラクトの中身は↓
https://github.com/RobinLinus/BitStream/blob/master/contract/burn_contract.md
上記の検証を行うために、Liquidの以下の機能を活用している:
- 任意のメッセージ(Claim)に対する署名検証を行う
OP_CHECKSIGFROMSTACK
- マークルパスの検証(マークルプルーフから部分的にマークルツリーを再構築してルートハッシュを比較する)に
OP_CAT
およびOP_MOD
、OP_DIV
- チャンクの復号に
OP_XOR
個人的に以下のコードブロックの連続で(ループができないので、必要と思われる数分このコードブロックが続く)、マークルツリーの復元してるのが面白かった。対象ノードのインデックスに対してOP_DIV
でこの次の計算で使用する該当ノードのインデックスを算出し、OP_MOD
で左右どちらか判定して必要に応じて、ノードの左右を入れ替えて、連結して内部ノードのハッシュ値を計算してるっぽい。
... // Loop begin OP_DEPTH OP_1SUB OP_IF OP_FROMALTSTACK OP_DUP <2> OP_DIV OP_TOALTSTACK <2> OP_MOD OP_NOTIF OP_SWAP OP_ENDIF OP_CAT OP_SHA256 OP_ENDIF ...
Bitcoinで実現するには?
↑はLiquidのL-BTCを使ったBond Contractだけど、これをBitcoinでやろうとすると必要なopcodeが無いので現状はできない*1。ペーパーでは、最低限OP_CAT
だけ導入すればBitcoinでも構成できると提案している。他のopcodeについての対応は↓
- XORの処理については、32ビットまでのデータであれば既存の算術opcodeを使ってエミュレートする。
- マークルプルーフの検証に使用するリーフのインデックスについては、bit文字列としてアンロックスクリプトで指定することで、
OP_MOD
をエミュレートする。 OP_CHECKSIGFROMSTACK
はどうするの?と思ったけど、Andrew Poelstraが以前紹介していたトリック↓を使って、OP_CAT
とSchnorr署名でエミュレートするみたい。
techmedia-think.hatenablog.com
BitStreamでできないこと
Fraud Proofによる不正の防止が組み込まれたBitStreamを利用することで、トラストレスな形で復号可能なコンテンツの鍵を支払いとアトミックに交換することができる。ただ、復号は保証されるが、その結果がユーザーが期待するコンテンツであるのかどうかまではBitStreamでは保証されない。そういう意味では、(Zero Knowledge Contingent PaymentやBitVMのようなステートメントの検証がないため)プロバイダーへのトラストが残る。