読者です 読者をやめる 読者になる 読者になる

Develop with pleasure!

福岡でCloudとかBlockchainとか。

BIP-70 Payment Protocol(訳)

techmedia-think.hatenablog.com

で書いた、Open Assets Extensions to Payment RequestsのベースとなってるBIP-70がどんな仕様なのかざっと訳してみる。(BIPBitcoin Improvement ProposalsはBitcoinの改良を提案している文書)

BIP-70は売り手と顧客のBitcoinを使った決済プロセス中の中間者攻撃を防ぐために定義されたプロトコル

動機

現在、Bitcoinの支払いは以下の最小ステップで成り立つ。

  1. 顧客がECサイトで商品をカートに入れ、決済手段としてBitcoinを選択する。
  2. 売り手は、支払いを受け取るためのユニークなBitcoinアドレスを生成し、顧客の注文と関連付け、顧客に支払いを行うか確認する。
  3. 顧客は売り手のサイトからBitcoinアドレスをコピーし、自分の使っているウォレットにペーストするか、bitcoinリンクをクリックして支払う金額がセットされた状態でウォレットを起動する。
  4. 顧客は売り手のBitcoinアドレスへの支払いを承認し、BitcoinP2Pネットワークを介してそのトランザクションをブロードキャストする。
  5. 売り手側のサーバが支払いを検知し、そのトランザクションの確認が充分取れたら(ブロックチェーン上での確認回数)取引を終了する。

BIP-70では、↓のような新機能をサポートするため↑のプロトコルを拡張する。

  1. 得体のしれない34bitのBitcoinアドレスに変わって"example.com"のような人が読める形の支払先を利用する。
  2. 顧客が売り手ともめた際に利用可能な、確実な支払いの証拠を提供する。
  3. ウォレットでトランザクションの承認が行われる前に中間者攻撃により売り手のアドレスが攻撃者のアドレスにすり替えられないようにする。
  4. 売り手が支払いのメッセージを受け取とり処理が行われたor行っていることをすぐに顧客が知ることができる。
  5. 過払いや何らかの理由で注文を処理できない場合に、売り手が顧客にコンタクトを取ることなく、自動的に払い戻し用のアドレスを提供する。
Protocol

このBIPで使用するPayment protocol messageは、GoogleのProtocol Bufferでエンコードされ、X.509証明書を使用し、http/https上で通信する。将来のBIPによってこのpayment protocolは他のエンコーディングPKI、転送プロトコルをサポートする。

このpayment protocolはPaymentRequest、Payment、PaymentACKという3つのメッセージで構成されており、顧客がなんらかの形で支払いの準備ができており、売り手のサーバがPaymentRequestメッセージを返すところから始まる。

https://github.com/bitcoin/bips/raw/master/bip-0070/Protocol_Sequence.png

Messages

Protocol Bufferのメッセージはpaymentrequest.protoに定義されてる。

Output

PaymentRequestメッセージ内で使用される出力は、支払い(もしくは一部の支払い)がどこに送られるのかを特定する。そして払い戻し先も同様にPaymentRequestメッセージに含める。

message Output {
    optional uint64 amount = 1 [default = 0];
    optional bytes script = 2;
}
amount 支払うSatoshi(0.00000001 BTC)の数
script 支払いを送る"TxOut"スクリプト。通常、標準のBitcoinトランザクションスクリプト(pubkey OP_CHECKSIGみたいな)の一部となる。これは将来このプロとコロルで、マスタ公開鍵とPaymentRequestのデータ自体から出力を算出するためのオプションとなる。
PaymentDetails/PaymentRequest

Payment requestは将来の拡張をサポートするため2つのメッセージに分割されている。大部分のメッセージはPaymentDetailsに含まれている。PaymentDetailsはPayment Requestメッセージに含まれており、売り手とデジタル署名についてのメタ情報を含んでいる。

message PaymentDetails {
    optional string network = 1 [default = "main"];
    repeated Output outputs = 2;
    required uint64 time = 3;
    optional uint64 expires = 4;
    optional string memo = 5;
    optional string payment_url = 6;
    optional bytes merchant_data = 7;
}
network Bitcionのプロダクション環境を意味する"main"か、Test netを意味する"test"のいずれかを指定する。クライアントがPayment Requestを受信した場合、そのプロトコルをサポートしていないのであればリジェクトしなければならない。
outputs Bitcoinであるからには1つ以上の出力が発生する。outputs.amountの合計が0の場合、顧客はいくら支払うのか確認され、Bitcoin clientは支払いのためどの(もしくは全ての)出力を選択するのか決める必要がある。outputs.amountが0でない場合は、顧客は合計額を確認し、支払いは0ではないOutputsで分割される必要がある。
time PaymentRequest が作られた時のUNIXタイムスタンプ
expires PaymentRequestが無効となるUNIXタイムスタンプ
memo このPaymentRequestが何のための支払いか顧客に説明するためのメモで、UTF-8エンコードされたプレーンテキスト
payment_url 後述するPaymentACKを得るためにPaymentメッセージが送信されるロケーション(通常はHTTPS
marchant_data PaymentRequestを識別するために売り手によって使われる任意のデータ。売り手が支払いとPaymentRequestを関連付ける必要がなかったり、PaymentRequestを別々のpayment addressに関連付けたりしている場合は省略可能。

PaymentDetails内で指定されたpayment_urlは、PaymentDetailsの有効期限内は有効なURLである必要がある。これはpayment requestがどんな状態にあるか関係なく注意しておかないといけない。例えば、注文のキャンセルが発生しても、売り手のサーバが払い戻しを行う際に、誤った支払いがあったことを記録しておくのが重要なので、payment_urlは無効にすべきではない。

PaymentRequestは、売り手のIDにオプションで紐付けられたPaymentDetailsである。

message PaymentRequest {
    optional uint32 payment_details_version = 1 [default = 1];
    optional string pki_type = 2 [default = "none"];
    optional bytes pki_data = 3;
    required bytes serialized_payment_details = 4;
    optional bytes signature = 5;
}
payment_details_version バージョン管理及びアップグレードについては以下参照。
pki_type 売り手を識別するためにPKIが使用される。全ての実装は"none","x509+sha256","x509+sha1"をサポートする必要がある。
pki_data 売り手を識別し、デジタル署名を作成するためのPKIデータ。X.509証明書の場合は、1つ以上のX.509証明書が含まれている。
serialized_payment_details protocol-bufferでシリアライズしたPaymentDetailsメッセージ
signature Protocol BufferでシリアライズされたPaymentRequestメッセージのハッシュのデジタル署名で、番号順に全てのフィールドがシリアライズされており、pki_date内の公開鍵に対応した秘密鍵で署名されている。設定されていない任意のフィールドはシリアライズされない。(ただ、デフォルト値が設定されているフィールドについてはシリアライズされ、署名にも影響する。)またシリアライズする前に、signatureフィールドは必ず空にしておく必要がある。(PaymentRequestの署名時にそのフィールドも含まれてしまうため)

BitcoinのウォレットアプリケーションはPaymentRequestを受け取ると、以下の操作を行って支払いを承認する必要がある。

  1. pki_typeが"none"以外の場合、売り手のIDと署名をPKIを使って検証する。
  2. 顧客のシステムのunix time (UTC)がPaymentDetails.expiresの時刻より前か検証する。もし、有効期限が切れていた場合は支払いのリクエストをリジェクトする。
  3. 顧客に売り手のIDを表示し支払いを行うか確認する。(表示例:最初のX.509証明書の"Common Name")

PaymentRequestのメッセージが50,000 bytesより大きい場合、ウォレットアプリはリジェクトを行う。(DoS攻撃緩和のため)

Payment

顧客が支払いを承認すると、Paymentメッセージが送られる。

message Payment {
    optional bytes merchant_data = 1;
    repeated bytes transactions = 2;
    repeated Output refund_to = 3;
    optional string memo = 4;
}
merchant_data PaymentDetails.merchant_dataからコピーした値で、売り手がPaymentRequestsへのPaymentを一致させるのに必要な、Invoiceの番号もしくは何らかのデータを使用する。※悪意のあるクライアントがmerchant_dataを変更することが可能なので、なんらかの方法で認証を行う必要がある。(例えば売り手だけが持ってる鍵で署名するとか)
transactions PaymentRequestへの支払いを行うにあたって有効な1つ以上の署名されたBitcoinトランザクション
refund_to 必要に応じて売り手が払い戻しをするための出力。売り手はpayment requestを送ってから2ヶ月以内であれば、これらの出力を使って払い戻しを行う。2ヶ月以上たってから払い戻しを行う場合は当事者間で別途交渉すること。
memo UTF-8エンコードされた、顧客から売り手へのプレーンテキストなメモ。

顧客が支払いを承認するとBitcoin clientは、

  1. PaymentDetails.outputsの支払いを満たす額の1つ以上のトランザクションを生成し、署名する。
  2. 顧客のシステムの unix time (UTC)がPaymentDetails.expiresの時刻より前か検証する。もし有効期限が切れていた場合は支払いをキャンセルする。
  3. トランザクションBitcoinP2Pネットワークにブロードキャストする。
  4. PaymentDetails.payment_urlが指定されていた場合、PaymentメッセージをそのURLにPOSTする。PaymentメッセージはシリアライズしPOSTリクエストのBODYにセットして送信する。

payment_urlに定義されているサーバとの通信でエラーが発生した場合は、ユーザにそのエラーを通知する。売り手側のサーバは個々のPaymentRequestについて同じPaymentメッセージを複数受信した場合は、それぞれ承認する必要がある。売り手のサーバから送られてくる2番目及びPaymentACKのメッセージは支払いの状態を示すmemoフィールドの値が異なる可能性がある(Bitcoinネットワーク上での確認回数の違いなどから)。これらは、データ送信中にトランスポートレベルの障害が発生した場合に取引を確実に行うため必要となる。障害が発生した場合は、Bitcoin Clientが再度Paymentを送ることでリカバリが可能になる。

PaymentDetails.payment_urlはPayment.refund_toを変更するような中間車攻撃に対してセキュアである必要がある。(もしHTTPを使ってる場合は、TLSで保護するようにすること)

ウォレットアプリはPaymentメッセージをHTTPで送信する際、BIP-71で定義されているように適切なContent-TypeとAccept Headerをセットする必要がある。

Content-Type: application/bitcoin-payment
Accept: application/bitcoin-paymentack

売り手のサーバがPaymentメッセージを受信すると、そのトランザクションが支払いの条件を満たしているかどうか判断する。

Paymentメッセージも(Payment Requestと同様)50,000 bytesより大きい場合、売り手のサーバによってリジェクトされる。(DoS攻撃緩和のため)

PaymentACK

PaymentACKはpayemnt protocolの最後のメッセージで、Paymentメッセージへのレスポンスとして売り手のサーバからBitcoinウォレットに送信される。

message PaymentACK {
    required Payment payment = 1;
    optional string memo = 2;
}
payment このPaymentACKをトリガーにしたPaymentメッセージのコピー。別の方法でPaymentsとPaymentACKsを関連付ける場合、クライアントはこれを無視してもいい。
memo UTF-8エンコードされたノートで、顧客にトランザクションの状態を表示させるために使用。(例:"Payment of 1 BTC for eleven tribbles accepted for processing.")

PaymentACKメッセージが60,000 bytesを超える場合はウォレットでリジェクトされるべき。(DoS攻撃対策)PaymentやPaymentRequestのメッセージ上限より大きいのは、PaymentACKが完全なPaymentッセージを含んでいるため。

Localization

売り手が複数の言語をサポートする場合、language-specific PaymentRequestsを生成し、言語とrequestを関連付けるかlanguage tagをrequestのmerchant_dataに埋め込む必要がある。また、リクエストの内容に応じて、 language-specific PaymentACKを生成する必要がある。

例えば、ギリシャの顧客がギリシャ版の売り手のサイト"Αγορά τώρα"リンクをクリックすると、merchant_dataに"lang=el&basketId=11252"をセットしたPaymentRequestを生成する。顧客は支払いを行い、顧客のBitcoin clientはPaymentメッセージを送信する。その後、売り手のサイトは PaymentACK.messageとして "σας ευχαριστούμε"を返す。

Certificates

PKIはデフォルトでX.509証明書を使用する。pki_typeが"x509+sha256" もしくは "x509+sha1"の場合のpki_dataのフォーマットは、Protocol Bufferでエンコードされた証明書チェーンとなる。

message X509Certificates {
    repeated bytes certificate = 1;
}

pki_typeが"x509+sha256"の場合、PaymentRequestメッセージはSHA256アルゴリズムでハッシュ化される。"x509+sha1"の場合はSHA1アルゴリズムが使われる。

各証明書は、DER [ITU.X690.1994] PKIX 証明書の値となる。PaymentRequestにデジタル署名したエンティティの公開鍵を含んでいる証明書は必ず最初に配置される必要がある。これは後続の各証明書がそれぞれ1つ前の証明書を保証し、ルート証明機関までたどる必要がある。信頼できるルート証明書が含まれているかもしれない。受信者はRFC 5280に沿って証明書チェーンを検証し、検証に失敗したらPaymentRequestをリジェクトする必要がある。

信頼されたルート証明書はOSから取得可能だが、もしOSの無いデバイスで検証が行われた場合は、 Mozilla root storeを使うことをお勧めする。

Extensibility

Protocol bufferのシリアライゼーションフォーマットは拡張可能なように設計されている。特に、新しくオプションフィールドをメッセージに追加するこができる。これは旧バージョンでは無視される(ただし、保存、再送信される)。

PaymentDetailsメッセージは新しいオプションフィールドを(version 1のまま)拡張できる。旧バージョンでは、新しいフィールドを含むPaymentRequestsに対して署名の検証はできるが、明らかに新しいオプションが追加されているにも関わらずそれが何なのかユーザに表示することはできない。

将来、売り手が新しい実装でしか受け入れられないPaymentRequestメッセージを生成したい場合は、version=2でPaymentDetailsメッセージを定義すれば良い。その際旧バージョンは、新しいバージョンのPaymentDetailsメッセージを受け取るとユーザにソフトウェアをアップデートすることを案内する必要がある。

この仕様でメッセージを拡張する必要がある実装は、1000から始まるタグを使用し、使用するタグについてはコンフリクトを避けるためextensions pageにプルリクを送ってページを更新すること。

References

BIP 0071:Payment Protocol mime types
BIP 0072:Payment Protocol bitcoin: URI extensions

Public-Key Infrastructure (X.509) working group : http://datatracker.ietf.org/wg/pkix/charter/

Protocol Buffers : https://developers.google.com/protocol-buffers/

Reference implementation

Payment Requestのジェネレータ: Handy-Dandy PaymentRequest Generator
BitcoinJ:Working with the BIP70 payment protocol API