Develop with pleasure!

福岡でCloudとかBlockchainとか。

Async Payjoinの仕様を定義するBIP-77

Payjoinは、送金トランザクションのインプットに送信者のインプットに加えて受信者のインプットを加えることで、トランザクションのインプットはすべて同一ユーザーが所有しているという「common input ownership」と呼ばれる分析のヒューリスティックを破壊する目的の提案。昔P2EPとも呼ばれていた機能。

ただこのPayjoinを行う場合、送信者と受信者が協力してトランザクションを構築する必要があるため、両者がオンラインであり、受信者はフルノードを実行し安全なパブリックエンドポイント(HTTPSやTorなど)をホストする必要があった。

Async Payjoin

このPayjoinのエンドポイントのホスト要件とオンライン要件をなくす提案がAsync Payjoin(サーバーレスPayjoinとも呼ばれてた)の提案BIP-77↓

https://github.com/bitcoin/bips/blob/master/bip-0077.md

Async Payjoinでは、受信者がエンドポイントをホストする代わりに、以下のように第三者がホストするPayjoinディレクトリサーバーを利用して、送信者と受信者が非同期にトランザクションを完成させる。送信者と受信者がやりとりするトランザクションの情報(PSBT)は、暗号化された状態でディレクトリサーバーに配置される。

sequenceDiagram
    title Async Payjoin シーケンス図
    participant R as 受信者
    participant D as ディレクトリ
    participant S as 送信者
    participant N as ネットワーク

    R-)S:  帯域外でPayjoin URI (BIP 21)を共有
    
    R-->>D: オリジナルPSBTを入手するために<br/>GETリクエストをポーリング
    activate D 
    S->>D: オリジナルPSBTをPOST
    D->>R: オリジナルPSBTをGETレスポンスで入手
    deactivate D

    S-->>D: プロポーザルPSBTを入手するために<br/>GETリクエストをポーリング
    activate D
    R->>D: プロポーザルPSBTをPOST
    D->>S: プロポーザルPSBTをGETレスポンスで入手
    deactivate D

    S->>N: Payjoinをブロードキャスト

処理フロー

Payjoin URI

まず受信者は、Payjoin URIを生成することから始める。ディレクトリにサブディレクトリの割当を要求し*1、ディレクトリ内のメールボックス(サブディレクトリ的な)のURLを、BIP-21またはBIP-321 URI内にpjパラメーターとして埋め込む。

pjパラメーターにはディレクトリのURLに加えて、以下のセッション固有のパラメーターも付与される。パラメーターの値はすべて大文字で、ディレクトリのURLとパラメーターの区切りは%23、各パラメーター同士は-で区切られる。また各セッション固有パラメーターは、bech32ライクにエンコードされており、HRPの後に「1」が続いてその後にbech32文字でエンコードされた値が続く(bech32と違ってチェックサムは付与されない)。なお、パラメーターは辞書順にソートされる。

  • EXパラメーター:セッションの有効期限を表すUNIX時間
  • OHパラメーター:ディレクトリのOHTTP鍵情報をエンコードしたデータ。2 バイト*2の鍵識別子+ディレクトリのゲートウェイの圧縮公開鍵。このパラメーターを含めることで、送信者が別にディレクトリの鍵を入手する必要がなくなる。
  • RKパラメーター:受信者の圧縮公開鍵。送信者は受信者のこの鍵を使ってHPKE(Hybrid Public Key Encryption)ベースの暗号化を行う。

結果、以下のようなURIが生成される。

bitcoin:tb1q6q6de88mj8qkg0q5lupmpfexwnqjsr4d2gvx2p?amount=0.00666666&pjos=0&pj=HTTPS://PAYJO.IN/TXJCGKTKXLUUZ%23EX1WKV8CEC-OH1QYPM59NK2LXXS4890SUAXXYT25Z2VAPHP0X7YEYCJXGWAG6UG9ZU6NQ-RK1Q0DJS3VVDXWQQTLQ8022QGXSX7ML9PHZ6EDSF6AKEWQG758JPS2EV

ディレクトリのURLのパスコンポーネント(↑の例だとTXJCGKTKXLUUZの部分)は、ショートIDと呼ばれ、ディレクトリ内のメールボックスの識別子。(HPKEの暗号化に使用する受信者の)圧縮公開鍵のSHA-256ハッシュ値を8 バイトに切り捨てた値。

相手がpjパラメーターを認識しない送信者の場合は、URI内のBitcoinアドレスへの支払いにフォールバックされる。

Async Payjoinフロー

Payjoin URIを準備した後のAsync Payjoinの具体的な流れは、

  1. 受信者はPayjoin URIを帯域外で送信者に送り、その後送信者からオリジナルPSBTがディレクトリに配置されるまでポーリングする。
  2. 送信者はまず、受け取ったURI内に記載されているBitcoinアドレス宛に送金するトランザクションを構築して署名を付与したブロードキャスト可能なオリジナルPSBTを作成する。このトランザクション自体はPayjoinしたトランザクションではなく単にフォールバック用のトランザクション。PSBTはBase64エンコードされ、その後にBIP-78で定義されているオプションパラメーターを付与できる。
  3. 続いて、送信者は一時鍵を生成し、その公開鍵リプライキーと呼び、圧縮公開鍵形式でシリアライズし、2のPSBTの先頭に配置する。
  4. 2,3のデータをPayjoin URIのRKパラメーターに記載されている受信者鍵を使ってHPKEベースで暗号化する。
  5. 4のデータをHPKEペイロード(下記PjV2MsgA)として、ディレクトリにPOSTする。送信者は、その後、受信者からプロポーザルPSBTがディレクトリに配置されるまでポーリングする。
  6. ポーリングしている受信者は、ディレクトリから送信者のオリジナルPSBTをGETし、復号してオリジナルPSBTを入手する。
  7. 受信者は、オリジナルPSBTに対し自身のインプットとアウトプットを追加して署名を付与したプロポーザルPSBTを作成する。これがPayjoinされたトランザクションになる。
  8. オリジナルPSBTの送信者の署名はプロポーザルPSBTに対しては無効なので、最終的に送信者に署名してもらうため、このプロポーザルPSBTを3の送信者のリプライキーを使って HPKEベースで暗号化する。
  9. 受信者は、送信者のリプライキーからショートIDを導出して、それをパスコンポーネントとしたURLに8のペイロード(下記PjV2MsgB)をPOSTする。
  10. ポーリングしている送信者は、ディレクトリから受信者のプロポーザルPSBTをGETし、復号し、自身の署名を加えて完成したトランザクションをBitcoinネットワークにブロードキャストする。
PjV2MsgA
 PjV2MsgA バイト表現 (合計7,168 バイト)
 +---------------------------------------------------------------------------------------+
 | ElligatorSwift |                                暗号文                                 |
 |   (64 バイト)   |                            (7104 バイト)                              |
 |                +-----------------------+---------------------------------+------------+
 |                |       リプライキー      |         パディングされた平文        |  AEADタグ  |
 |                |       (33 バイト)      |   (7055 バイト = 7168-64-33-16)  | (16 バイト) |
 +---------------------------------------------------------------------------------------+
PjV2MsgB
 PjV2MsgB バイト表現 (合計7,168 バイト)
 +---------------------------------------------------------------------------------------+
 | ElligatorSwift |                                暗号文                                 |
 |   (64 バイト)   |                            (7104 バイト)                              |
 |                +-----------------------+---------------------------------+------------+
 |                |                       パディングされた平文                  |  AEADタグ  |
 |                |                     (7088 バイト = 7168-64-16)           | (16 バイト) |
 +---------------------------------------------------------------------------------------+

以上が、Async Payjoinのフロー。

OHTTP

↑のようなディレクトリサーバーを利用する際に、IPなどのユーザーの識別情報がディレクトリに収集されないよう、Async PayjoinではOHTTP(Oblivious HTTP)プロトコルを使用するようになっている。OHTTPについては以下の解説記事がわかりやすい↓

asnokaze.hatenablog.com

受信者/送信者とディレクトリのOHTTPベース通信は具体的には以下のようになる。

 sequenceDiagram
   title OHTTPシーケンス図
   participant C as クライアント
   participant R as OHTTPリレー
 
   box PaleVioletRed Payjoinディレクトリ
     participant G as OHTTPゲートウェイ
     participant D as HTTPリソース
   end
 
   C->>R: FROM: Client IPからリレーリクエスト<br/>[+ カプセル化されたリクエスト]
   R->>G: Gateway Request<br/>FROM: Relay IP<br/>[+ カプセル化されたリクエスト]
   G->>D: リクエスト
   D->>G: レスポンス
   G->>R: リレーIPへのゲートウェイレスポンス<br/>[+ カプセル化されたレスポンス]
   R->>C: クライアントIPへのリレーレスポンス<br/>[+ カプセル化されたレスポンス]

2層のHPKE

Async Payjoinでは、以下の2層でHPKEを用いた暗号化が行われる。

  • 受信者-送信者間のHPKE
    • オリジナルPSBT/プロポーザルPSBTを受信者/送信者の鍵で暗号化
  • OHTTP層でのHPKE
    • 暗号化されたPSBTを含むHTTPリクエスト全体をディレクトリ/クライアント(受信者/送信者)の鍵で暗号化

参照実装

BIP-78 Payjoin V1とこのBIP-77 Payjoin V2の両方をサポートするRustで書かれたPayjoin Dev Kitライブラリが公開されている↓

https://github.com/payjoin/rust-payjoin

中には、payjoin-directoryという↑のディレクトリサーバの参照実装も含まれてる。

ディレクトリを運用するメリットは?

↑のような仕組みで、受信者がパブリックエンドポイント要件が排除されたものの、じゃあディレクトリの運用者のインセンティブは?となる。

最近Payjoin Foundationが設立されたみたいなので↓、そういったところが助成していくのか、それもとも少額の支払いで利用可能なディレクトリが出てくるのか?

insider.btcpp.dev

*1:ディレクトリへのDoSを防止するため、認証トークンとセットで

*2:rfc9458だと識別子は8 bitになってるけど何で2 バイトなんだろう?