Develop with pleasure!

福岡でCloudとかBlockchainとか。

支払いの証明(Proof of Payment)について定義したBIP-120

Bitcoin決済をしたことを証明する支払いの証明(Proof of Payment)方法について定義しているのがBIP-120↓

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

Bitcoinによる支払いが行われたことを条件にサービスなどを提供するケースって結構あるんじゃないかなー。

(BIP-120自体はコンセンサスなどとは無関係なApplicationsレイヤーのBIPなので、デプロイなどは無い。)

動機

支払いの証明(Proof of Paymant = PoP)を利用することで以下のようなユースケースが考えられる。

  • ホテルなどで既に料金を支払っている場合に、そのPoPがドアの鍵になる。
  • オンラインのビデオサービスでビデオの代金を支払っていれば、どのデバイスでも視聴できる。
  • 事前に支払い済みの広告。広告の掲載期間内であればいつでも、PoPを使って新しいコンテンツを広告としてアップロードできる。
  • PoPを使って有料サイトにログインする。
  • 毎月支払いをする駐車場や車の認証をPoPを使って行う。
  • 全参加者が同じアドレスに支払いをする宝くじで、勝者はそのアドレスへ支払いをしたトランザクションの中から選択される。勝者はそのトランザクションのPoPと賞金を交換する。

Proof of Paymentを使うと個人情報(ユーザー名やパスワード、メールアドレス)などを必要とせず、こういったユースケースを実現できる。

論拠

PoPを実現する上で必要な特性:

  1. PoPは必要に応じて生成する。
  2. 盗難リスクを避けるため基本的に一度しか使用できない。
  3. (P2SHやP2PKHなどの)スクリプトタイプに関係なく、支払いのためをPoPを作成できる必要がある。
  4. 実証済みのトランザクションの全入力のロックを解除するのに、十分な資格を持っていることを証明する必要がある。
  5. 簡単に採用できるようウォレットとサーバーの実装は簡単でなければならない。

支払いを証明するための現在の方法:

  • BIP-70では、リクエストを満たすトランザクションとともに、PaymentRequestが何らかの証明をするが、1,2もしくは4は満たしておらず、BIP-70では3しかできない。そのため証明を要求/提供する標準的な方法は定義されていない。標準化すれば5を満たせる。
  • サーバーが生成したメッセージに、トランザクションに署名するのに使う秘密鍵を使って署名する。これは1と2を満たすかもしれないが、おそらく3は満たせない。これも標準化はされていない。4は設計されていれば満たせる。

仕様

データ構造

トランザクションTの支払いに対する証明(PoP(T)とする)は、Tの全ての入力をアンロックするのに必要な情報を所有していることを証明するデータでもある。PoP(T)の入力は基本的にTと同じトランザクション構造(同じ入力を同じ順で持つ)をもつデータで、入力データのうちシーケンス番号だけは元と違い0がセットされている。またpop outputと呼ばれるコインの量が0の出力を1つ持つ。このpop outputの形式は↓

OP_RETURN <version> <txid> <nonce>
フィールド バイト数 内容
version 2 バージョン。リトルエンディアンで現在は0x01 0x00
txid 32 証明するトランザクションのtxid
nonce 6 ランダムデータ

PoPのロックタイムは必ず499999999にセットする必要がある。これは誤ってPoPがBitcoinP2Pネットワークに流れても、ブロックに取り込まれることが無いようにするため。シーケンス番号がffffffffだとロックタイムを無効にするので、シーケンス番号に0をセットしているのもそのため。この仕様では全入力のシーケンス番号を0にするようにしている。ロックタイムを有効にするのには1つでも十分だけど、シンプルにするため。

TとPoP(T)の構造はそれぞれ以下のようになる。

  T
 +------------------------------------------------+
 |inputs                | outputs                 |
 |       Value,Sequence | Value,Script            |
 +------------------------------------------------+
 |input0 1,ffffffff     | 0,pay to A              |
 |input1 3,ffffffff     | 2,OP_RETURN <some data> |
 |input2 4,ffffffff     | 1,pay to B              |
 |                      | 4,pay to C              |
 +------------------------------------------------+
 
  PoP(T)
 +-------------------------------------------------------------+
 | inputs               | outputs                              |
 |       Value,Sequence | Value,Script                         |
 +-------------------------------------------------------------+
 |input0 1,00000000     | 0,OP_RETURN <version> <txid> <nonce> |
 |input1 3,00000000     |                                      |
 |input2 4,00000000     |                                      |
 +-------------------------------------------------------------+
 | lock_time=499999999                                         |
 +-------------------------------------------------------------+

PoPはBitcoinトランザクションと同じ署名プロセスで署名される。

nonceは盗まれたPoPの使用を困難にするための要素で、サーバーがPoPのリクエスト毎に新しいnonceを生成することで、盗まれたPoPを無効化する。

プロセス

  1. 最初にProof of Paymentのリクエストがサーバーからウォレットに送られる。このPoPリクエストには、以下が含まれる。
    1. ランダムなnonce
    2. PoPの送信先httpsなURLなど)
    3. どのトランザクションのproofを作ればいいのか示すウォレットへのヒントで、例えば
      • (サーバーが知っていれば)txid
      • (BIP-70の支払いの場合は)PaymentRequest.PaymentDetails.merchant_data
      • BIP-21のURIから得られる量、ラベル、メッセージやその他の情報
  2. ウォレットはサーバーの情報からトランザクションを特定する。ウォレット単体で特定できない場合は、1-3のヒントをユーザーに確認し、一致するものを選択してもらう。
  3. ウォレットは特定したトランザクションの未署名のUPoPを作成し、ユーザーに署名するか尋ねる。
  4. ユーザーは署名するか確認する
  5. ウォレットはUPoPに署名しPoPを作成する
  6. PoPを1の宛先に送信する
  7. サーバーは受信したPoPを検証し、その結果をvalidinvalidで返す
  8. ウォレットは何らかの方法でユーザーにレスポンスを表示する。

備考

  • ステップ1のPoPリクエストを送信する方法についてはここでは指定しない。BIP-121を参照。
  • nonceは新しいPoPリクエストごとにサーバによってランダムに生成される必要がある。

PoPの検証

サーバーはPoPを検証し、validinvalidを返す必要がある。この検証プロセスの概略を以下に示す。いずれかのステップでも失敗すると検証は途中で中止され、invalidが返される。

  1. PoPのフォーマットをチェックする。入力が使用済みであることを除いて、通常のトランザクションの検証をパスする必要がある。
  2. ロックタイムが499999999かチェックする。
  3. 出力は1つだけかチェックする。この出力のコインの量は必ず0で、上記のOP_RETURN出力フォーマットに準拠している必要がある。
  4. nonceがリクエストで送信したものと同じかチェックする。
  5. シーケンス番号が0であること以外は、PoPの入力が全てトランザクションTと全く同じであることを確認する(署名は除外)。この時入力の順序もTと同じこと。
  6. 全入力のスクリプトを実行し、全てtrueを返すこと。
  7. PoP出力に書かれているtxidが、実際に証明対象のトランザクションであるかチェックする。もし、どのトランザクションか知らない場合は、実際の製品やサービスに支払われたトランザクションを確認すること。
  8. 上記全てパスしたらvalidを返す。

セキュリティの考慮事項

  • 誰かがPoPリクエストをインターセプトし、その中のパラメータを変更する可能性があるが、これはSSLなどの安全な接続を使うことで軽減できる。以下のような改竄が考えられる↓
    • PoPの送付先を変更し、PoPを盗む
    • labelを変更し、意図しないPoPに署名したり、ウォレットに記録がないラベルが設定されるとサービスが利用できなくなる。
    • nonceを変更することで、PoPはサーバー上の検証で失敗する。
  • PoPリクエストを改竄してPoPを盗み、nonceが一致するまでサーバにリクエストを送る。実際に合致する確率は1 / 248。サーバーにはこの種の総当り攻撃を検出する仕組みを用意するか、少なくともPoPリクエストを100ms遅らせるなどしてプロセスを遅くする。
  • ウォレットに資金がない場合でも、PoP生成器としての価値がある可能性がある。そのためウォレットの残高が0でもセキュリティを維持することは重要。
  • トランザクションのmalleabilityによりサーバーとクライアントで異なるtxidを認識している可能性がある。その場合、クライアントはサーバーに対しトランザクションの証明ができない。ウォレットは自身がブロードキャストした時のtxidに依存してはいけない。代わりにネットワーク上のトランザクションをチェックし、そのトランザクションをリストに加えるなければならない。

参照実装

所感

  • 特殊なスクリプトなんかを設計すると、スクリプトタイプに依存して署名方法が複雑になったりするので、↑みたいに無効なトランザクション作って署名するパータンはありだなー。
  • 入力順も検証ルールに入ってるのは何でかな?単純にシンプルにしたいから?
  • 今のプロトコル仕様のままでも結構利用シーンありそうだけど、そんなに話題に上がらないのは別にもっと良いプロトコルある?